[
  {
    "path": ".clang-format",
    "content": "---\n# See https://releases.llvm.org/14.0.0/tools/clang/docs/ClangFormatStyleOptions.html\nBasedOnStyle: Chromium\nAccessModifierOffset: -4\nAlignAfterOpenBracket: AlwaysBreak\n# AlignArrayOfStructures can cause crashes, see https://github.com/llvm/llvm-project/issues/55269\n#AlignArrayOfStructures: Left\nAllowAllParametersOfDeclarationOnNextLine: false\nAllowShortBlocksOnASingleLine: Empty\nAllowShortFunctionsOnASingleLine: All\nBinPackArguments: false\nBinPackParameters: false\nBreakBeforeBinaryOperators: NonAssignment\nBreakBeforeBraces: Custom\nBraceWrapping:\n  # NB: due to https://github.com/llvm/llvm-project/issues/55582 the Multiline setting will not\n  # always work (should be fixed in clang-format 15, but that is not available as a python wheel yet\n  # due to https://github.com/ssciwr/clang-format-wheel/issues/49)\n  AfterControlStatement: MultiLine # makes sure multiline ifs don't run into their bodies\n  AfterFunction: true # makes constructors with initialisers much nicer\nBreakBeforeConceptDeclarations: true\nBreakBeforeTernaryOperators: true\nBreakConstructorInitializers: BeforeComma\nBreakStringLiterals: true\nColumnLimit: 100\nCompactNamespaces: true\nConstructorInitializerIndentWidth: 0\nContinuationIndentWidth: 4\nCpp11BracedListStyle: true\nDerivePointerAlignment: false # force use of the PointerAlignment setting\nFixNamespaceComments: true\nIncludeBlocks : Regroup\nIncludeCategories:\n  # Aim is:\n  # 0. the \"main\" header file (#include \"foo.h\" in foo.cpp) automatically gets priority 0\n  # 1. internal headers (#include \"util/helpers.h\"): quotation marks, with a '/'\n  # 2. third-party headers (#include <arrow/status.h>): angle brackets, '/' or .h/.hpp/.h++\n  #    file ext\n  # 3. standard library headers (#include <vector>): angle brackets, no file ext, no '/'\n  - Regex:    '^\"'\n    Priority: 1\n  - Regex:    '^<.*/'\n    Priority: 2\n  - Regex:    '\\.h>'\n    Priority: 2\n  - Regex:    '\\.hpp>'\n    Priority: 2\n  - Regex:    '\\.h\\+\\+>'\n    Priority: 2\nIncludeIsMainRegex: '(_test|_tests|Tests|Test)?$'\n  # foo.h will be considered the \"main\" header (and sorted to the top) for all of the following:\n  # - foo.cpp\n  # - foo_test.cpp\n  # - foo_tests.cpp\n  # - fooTests.cpp (although this is intended for Foo.h and FooTests.cpp)\n  # - fooTest.cpp (although this is intended for Foo.h and FooTest.cpp)\nIndentCaseLabels: false\nIndentWidth: 4\nInsertBraces: true\nPackConstructorInitializers: CurrentLine\nPointerAlignment: Middle\nQualifierAlignment: Right # const east\n# clang 14 *should* know about QualifierOrder (according to its docs) but claims it doesn't\n#QualifierOrder: ['static', 'constexpr', 'inline', 'type', 'const', 'volatile', 'restrict']\nReflowComments: false\nSeparateDefinitionBlocks: Always\nSortIncludes: CaseInsensitive\nSortUsingDeclarations: true\nSpaceAroundPointerQualifiers: Before\nStandard: c++20\n"
  },
  {
    "path": ".codespellrc",
    "content": "# Waiting for pyproject.toml support: https://github.com/codespell-project/codespell/issues/2055\n\n[codespell]\n# \"write-changes\" doesn't work with \"ignore-regex\"\n# https://github.com/codespell-project/codespell/issues/2056\n# comma-separated list of built-in dictionaries (default is \"clear,rare\")\nbuiltin = clear,rare,code\n# show the line in which the error occurred\ncontext = 0\n# these options are turned on by specifying them here\ncheck-filenames =\ncheck-hidden =\nenable-colors =\n# split words on underscores\n# e.g. \"foo_bar\" is split into two words (\"foo\", \"bar\") instead of one word (\"foo_bar\")\nignore-regex = _\n# comma-separated list of false positives\nignore-words-list = iff,inout,befores,deque,stdio,O_WRONLY,wronly,sv_lite,lite,creat,arange\n# comma-separated list of globs of files not to check\nskip = .gitignore,.codespellrc\n"
  },
  {
    "path": ".flake8",
    "content": "# Waiting for pyproject.toml support: https://gitlab.com/pycqa/flake8/-/issues/428\n\n[flake8]\nextend-ignore = E203, W503\nmax-line-length = 120\nper-file-ignores = __init__.py:F401, __init__.py:F403\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Based on https://github.com/alexkaratarakis/gitattributes\n# Auto detect text files and force linux-style line endings\n*     text=auto eol=lf\n# Documents\n*.bibtex   text diff=bibtex\n*.doc      diff=astextplain\n*.DOC      diff=astextplain\n*.docx     diff=astextplain\n*.DOCX     diff=astextplain\n*.dot      diff=astextplain\n*.DOT      diff=astextplain\n*.pdf      diff=astextplain\n*.PDF      diff=astextplain\n*.rtf      diff=astextplain\n*.RTF      diff=astextplain\n*.md       text diff=markdown\n*.mdx      text diff=markdown\n*.tex      text diff=tex\n*.adoc     text\n*.textile  text\n*.mustache text\n*.csv      text\n*.tab      text\n*.tsv      text\n*.txt      text\n*.sql      text\n*.epub     diff=astextplain\n# Graphics\n*.png      binary\n*.jpg      binary\n*.jpeg     binary\n*.gif      binary\n*.tif      binary\n*.tiff     binary\n*.ico      binary\n*.svg      binary\n*.eps      binary\n*.bash     text\n*.fish     text\n*.sh       text\n*.zsh      text\n# These are explicitly windows files and should use crlf\n*.bat      text eol=crlf\n*.cmd      text eol=crlf\n*.ps1      text eol=crlf\n# Serialisation\n*.json     text\n*.toml     text\n*.xml      text\n*.yaml     text\n*.yml      text\n# Archives\n*.7z       binary\n*.gz       binary\n*.tar      binary\n*.tgz      binary\n*.zip      binary\n# Text files where line endings should be preserved\n*.diff     -text\n*.patch    -text\n# Exclude git(lab)-specific files when making an archive of the source tree\n.gitattributes          export-ignore\n.gitignore              export-ignore\n.gitkeep                export-ignore\n.git-blame-ignore-revs  export-ignore\n.gitlab-ci.yml          export-ignore\n/ci                     export-ignore\n# C++ Sources\n*.c        text diff=cpp\n*.cc       text diff=cpp\n*.cxx      text diff=cpp\n*.cpp      text diff=cpp\n*.c++      text diff=cpp\n*.hpp      text diff=cpp\n*.h        text diff=cpp\n*.h++      text diff=cpp\n*.hh       text diff=cpp\n# Read formats\n*.pod5 filter=lfs diff=lfs merge=lfs -text\n*.fast5    binary\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n## Issue Description\n\n> Please provide a description of your issue and include any commands used to reproduce the issue.\n\n## Logs\n\n> Please provide any log files. These can be generated by setting the `POD5_DEBUG` environment variable e.g. `POD5_DEBUG=1 pod5 view my.pod5`\n\n## Specifications\n\n- Pod5 Version:\n- Python Version:\n- Platform:\n"
  },
  {
    "path": ".gitignore",
    "content": "build*/\n.conan/\ncmake-build*/\nCMakeUserPresets.json\n_build/\n.conan/\n.cache/\ndist/\n.DS_Store\n.pod5\nvenv/\n*.venv/\nuv.lock\ndocs/public/\n.tmp_pod5*\n_version.py\n*egg-info/\nPOD5Version.cmake\n*.swp\ntest_package/CMakeUserPresets.json\n.vscode/\n.devcontainer/\n__pycache__\npython/Python.framework/\n/fuzz/corpus_*\n"
  },
  {
    "path": ".gitlab-ci.yml",
    "content": "stages:\n  - .pre\n  - build\n  - test\n  - build-conan\n  - archive\n  - deploy\n\ninclude:\n    - local: '/ci/gitlab-ci-common.yml'\n\nvariables:\n  GIT_SUBMODULE_STRATEGY: recursive\n  STABLE_BRANCH_NAME: master\n  DO_UPLOAD: \"yes\" # Always upload in conan upload jobs (only run on tags)\n  CONAN_PROFILE_BUILD_TYPE: Release\n  CONAN_VENV_PYTHON: \"3.13\"\n  CMAKE_VERSION: \"4.2.3\"\nbefore_script:\n  - \"\"\n\n# The versions that we build and test.\n.parallel-py-versions:\n  parallel:\n    matrix:\n      - PYTHON_VERSION: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n\n\n# ======================================\n#\n#     Docker\n#\n# ======================================\n\n\n.build-docker-image:\n  stage: .pre\n  image: ${CI_REGISTRY}/traque/ont-docker-base/ont-base-docker:latest\n  before_script:\n    - docker login --username ${CI_REGISTRY_USER} --password ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY}\n  when: manual\n  retry:\n    max: 2\n    when: runner_system_failure\n  script:\n    - tag=\"${CI_REGISTRY_IMAGE}/${IMAGE_TAG}\"\n    - docker image build --pull --target \"${PLATFORM}\"\n                         --tag \"${tag}\" --file ${DOCKERFILE} ci/docker\n    - docker image push ${tag}\n\ndocker base aarch64:\n  tags:\n    - docker-builder-arm\n  extends:\n    - .build-docker-image\n  variables:\n    IMAGE_TAG: \"build-arm64\"\n    DOCKERFILE: \"ci/docker/Dockerfile.py39.arm64\"\n\ndocker base x86-64:\n  tags:\n    - docker-builder\n  extends:\n    - .build-docker-image\n  variables:\n    IMAGE_TAG: \"build-x64\"\n    DOCKERFILE: \"ci/docker/Dockerfile.py39.x64\"\n\ndocker conda:\n  tags:\n    - docker-builder\n  extends:\n    - .build-docker-image\n  variables:\n    IMAGE_TAG: \"conda\"\n    DOCKERFILE: \"ci/docker/Dockerfile.conda\"\n\n\n.docker template:\n  stage: docker\n  image: ${CI_REGISTRY}/traque/ont-docker-base/ont-base-docker:latest\n  before_script:\n    - docker login --username ${CI_REGISTRY_USER} --password ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY}\n  retry:\n    max: 2\n    when: runner_system_failure\n\n# ======================================\n#\n#     Versioning\n#\n# ======================================\n\nprepare_version:\n  stage: .pre\n  image: ${CI_REGISTRY}/traque/ont-docker-base/ont-base-python:3.10\n  script:\n    - git tag -d $(git tag -l \"*a*\")\n    - git tag -d $(git tag -l \"*b*\")\n    - git tag -d $(git tag -l \"*r*\")\n    - git tag -d $(git tag -l \"*c*\")\n    - git tag -d $(git tag -l \"*dev*\")\n    - if [[ ${CI_COMMIT_TAG/#v/} && -z $( git tag -l \"${CI_COMMIT_TAG/#v/}\" ) ]]; then git tag ${CI_COMMIT_TAG/#v/}; fi\n    - pip install --upgrade pip setuptools_scm~=7.1\n    - apt update && apt install -y git-lfs\n    - git status --porcelain\n    - python -m setuptools_scm\n    - cat _version.py\n    # Show the version that will be used in the pod5/pyproject.toml\n    - VERSION=$(grep \"__version__\" _version.py | awk '{print $5}' | tr -d \"'\" | cut -d'+' -f1)\n    - echo $VERSION\n    - python -m pod5_make_version\n    - cat cmake/POD5Version.cmake\n    - cat _version.py python/lib_pod5/src/lib_pod5/_version.py\n    - cat _version.py python/pod5/src/pod5/_version.py\n  artifacts:\n    name: \"${CI_JOB_NAME}-artifacts\"\n    paths:\n      - \"cmake/POD5Version.cmake\"\n      - \"_version.py\"\n      - \"python/lib_pod5/src/lib_pod5/_version.py\"\n      - \"python/pod5/src/pod5/_version.py\"\n\n\n# ======================================\n#\n#     Pre-Flight Setup / Checks\n#\n# ======================================\n\ntag_version_check:\n  stage: .pre\n  needs:\n    - \"prepare_version\"\n  only:\n    - tags\n  image: ${CI_REGISTRY}/minknow/images/build-x86_64-gcc13:latest\n  script:\n    - uv venv .venv\n    - source .venv/bin/activate\n    - uv pip install \"cmake==${CMAKE_VERSION}\"\n    - pod5_version=\"$(cmake -P ci/get_tag_version.cmake 2>&1)\"\n    - tag_version=\"${CI_COMMIT_TAG/#v/}\"\n    - if [[ \"${pod5_version}\" != \"${tag_version}\" ]]; then\n        echo \"Tag is for release ${tag_version}, but POD5 version is $pod5_version\";\n        exit 1;\n      fi\n\napi_lib_version_check:\n  stage: .pre\n  needs:\n    - \"prepare_version\"\n  image: ${CI_REGISTRY}/minknow/images/build-x86_64-gcc13:latest\n  script:\n    - cat _version.py\n    - NO_DEV_VERSION=$(grep \"__version__\" _version.py | awk '{print $5}' | tr -d \"'\" | cut -d'+' -f1 | sed 's/\\([0-9]\\+\\.[0-9]\\+\\.[0-9]\\+\\).*$/\\1/')\n    - echo $NO_DEV_VERSION\n    - cat python/pod5/pyproject.toml\n    - echo \"If this jobs fails then we have forgotten to match the api and lib version in the api python/pod5/pyproject.toml\"\n    - grep \"lib_pod5\\s*==\\s*$NO_DEV_VERSION\" python/pod5/pyproject.toml\n\n\npre-commit checks:\n    image: ${CI_REGISTRY}/traque/ont-docker-base/ont-base-python:3.10\n    stage: .pre\n    tags:\n        - linux_x86\n        - docker\n    script:\n        - pip install pre-commit\n        - if ! pre-commit run --all-files; then\n        -   cat \"${PRE_COMMIT_HOME}/pre-commit.log\"\n        - >-\n            if grep -F -q \\\n              -e \"InvalidManifestError\" \\\n              -e \"error: [Errno 17] File exists: 'build/temp.linux-x86_64-cpython-\" \\\n              \"${PRE_COMMIT_HOME}/pre-commit.log\"; then\n        -     echo \"Bad cache state detected, deleting cache and re-running\"\n        -     rm -rf \"${PRE_COMMIT_HOME}/\"\n        -     pre-commit run --all-files\n        -   else\n        -     exit 1\n        -   fi\n        - fi\n    after_script:\n        - cat \"${PRE_COMMIT_HOME}/pre-commit.log\" || true\n    variables:\n        PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit\n    cache:\n        paths:\n            - ${PRE_COMMIT_HOME}\n\n\n# ======================================\n#\n#     Build Lib Standalone\n#\n# ======================================\n\n\nbuild-standalone-ubu22:\n  stage: build\n  image: external-docker.artifactory.oxfordnanolabs.local/ubuntu:22.04\n  needs:\n    - \"prepare_version\"\n  script:\n    - export DEBIAN_FRONTEND=noninteractive\n    - apt-get update\n    - apt-get install -y -V ca-certificates lsb-release wget\n    - wget https://apache.jfrog.io/artifactory/arrow/$(lsb_release --id --short | tr 'A-Z' 'a-z')/apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb\n    - apt-get install -y -V ./apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb\n    - apt-get update\n    - apt-get install -y cmake build-essential libzstd-dev libzstd-dev libflatbuffers-dev libarrow-dev=18.0.0-1\n    - mkdir -p build\n    - cd build\n    - cmake\n      -D POD5_DISABLE_TESTS=OFF\n      -D POD5_BUILD_EXAMPLES=ON\n      -D BUILD_PYTHON_WHEEL=OFF\n      ..\n    - cmake --build . --parallel\n    - ctest -C Release -VV\n\n\n# ======================================\n#\n#     Build helpers\n#\n# ======================================\n\n\n# Takes CMAKE_ARGS, AUDITWHEEL_PLATFORM, and PYTHON_VERSION.\n.conan-build-and-test:\n  - |\n  - export TOOLCHAIN_FILE=build/generators/conan_toolchain.cmake\n  - pod5_version=\"$(cmake -P ci/get_tag_version.cmake 2>&1)\"\n  - mkdir -p build\n  - cd build\n  - ${conan_exe} install --profile ${CONAN_PROFILE} ${EXTRA_INSTALL_ARGS} ..\n  - cmake ${CMAKE_ARGS}\n    -D BUILD_SHARED_LIB=ON\n    -D CMAKE_BUILD_TYPE=Release\n    -D POD5_DISABLE_TESTS=OFF\n    -D POD5_BUILD_EXAMPLES=ON\n    -D BUILD_PYTHON_WHEEL=OFF\n    -D CMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE}\n    ..\n  - cmake --build . --config Release --parallel\n  - ctest -C Release -VV\n  - ../ci/install.sh\n  - cmake ${CMAKE_ARGS}\n    -D BUILD_SHARED_LIB=OFF\n    -D CMAKE_BUILD_TYPE=Release\n    -D POD5_DISABLE_TESTS=OFF\n    -D POD5_BUILD_EXAMPLES=ON\n    -D BUILD_PYTHON_WHEEL=ON\n    -D PYTHON_VERSION=${PYTHON_VERSION}\n    -D CMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE}\n    ..\n  - cmake --build . --config Release --parallel\n  - ctest -C Release -VV\n  - ../ci/install.sh STATIC_BUILD\n  - ../ci/package.sh ${OUTPUT_SKU} ${AUDITWHEEL_PLATFORM}\n\n\n# ======================================\n#\n#     Build Lib Linux\n#\n# ======================================\n\n\n.build-linux:\n  stage: build\n  needs:\n    - \"prepare_version\"\n  variables:\n    EXTRA_INSTALL_ARGS: \"-o arrow:with_boost=False -o arrow:with_thrift=False -o arrow:parquet=False\"\n  before_script:\n    - /opt/python/cp310-cp310/bin/pip install -U pip 'conan<2' auditwheel build \"cmake==${CMAKE_VERSION}\"\n    - ln -n /opt/python/cp310-cp310/bin/auditwheel /usr/bin/auditwheel\n    - ln -n /opt/python/cp310-cp310/bin/conan /usr/bin/conan\n    - conan config install --verify-ssl=no ${CONAN_CONFIG_URL}\n    - conan_exe=$(which conan)\n  script:\n    - !reference [\".conan-build-and-test\"]\n  artifacts:\n    name: \"${CI_JOB_NAME}-artifacts\"\n    paths:\n      - \"lib_pod5*.tar.gz\"\n      - \"lib_pod5*.whl\"\n\nlinux-x64-gcc9-release-build:\n  image: external-quay.artifactory.oxfordnanolabs.local/pypa/manylinux2014_x86_64\n  extends:\n    - .build-linux\n    - .parallel-py-versions\n  tags:\n    - linux\n  variables:\n    CONAN_PROFILE: \"linux-x86_64-gcc9.jinja\"\n    CONAN_PROFILE_CPPSTD: 17\n    OUTPUT_SKU: \"linux-x64\"\n    AUDITWHEEL_PLATFORM: manylinux2014_x86_64\n\nlinux-aarch64-gcc9-release-build:\n  image: external-quay.artifactory.oxfordnanolabs.local/pypa/manylinux2014_aarch64\n  extends:\n    - .build-linux\n    - .parallel-py-versions\n  tags:\n    - linux_aarch64\n    - high-cpu\n  variables:\n    CONAN_PROFILE: \"linux-aarch64-gcc9.jinja\"\n    CONAN_PROFILE_CPPSTD: 17\n    OUTPUT_SKU: \"linux-arm64\"\n    AUDITWHEEL_PLATFORM: manylinux2014_aarch64\n\n\n# ======================================\n#\n#     Build Lib OSX\n#\n# ======================================\n\n\n.build-osx-common:\n  stage: build\n  needs:\n    - \"prepare_version\"\n  variables:\n    EXTRA_INSTALL_ARGS: \"-o arrow:with_boost=False -o arrow:with_thrift=False -o arrow:parquet=False\"\n  before_script:\n    - uv venv .venv_conan --python ${CONAN_VENV_PYTHON} --seed\n    - source .venv_conan/bin/activate\n    # Note that cmake 3.31+ do not work properly on macOS 14 (and earlier)\n    # Pinning to 3.30 avoid SSL issues when connecting to internal servers\n    - uv pip install -U pip 'conan<2' 'cmake==3.30.9'\n    - conan config install --verify-ssl=no \"${CONAN_CONFIG_URL}\"\n    - conan_exe=$(which conan)\n    - uv python install ${PYTHON_VERSION}\n    - uv venv --python \"python${PYTHON_VERSION}\" .venv --seed\n    - source .venv/bin/activate\n    - which python\n    - python --version\n  script:\n    - python3 -c \"import sysconfig; print(sysconfig.get_platform())\"\n    - !reference [\".conan-build-and-test\"]\n  artifacts:\n    name: \"${CI_JOB_NAME}-artifacts\"\n    paths:\n      - \"lib_pod5*.tar.gz\"\n      - \"lib_pod5*.whl\"\n\nosx-arm64-clang15-release-build:\n  extends:\n    - .build-osx-common\n    - .parallel-py-versions\n  tags:\n    - osx_arm64\n    - xcode-15.3\n    - conan\n  variables:\n    CONAN_PROFILE: \"macos-aarch64-appleclang-15.0.jinja\"\n    CONAN_PROFILE_CPPSTD: 20\n    CMAKE_ARGS: \"-DCMAKE_OSX_ARCHITECTURES=arm64\"\n    MACOSX_DEPLOYMENT_TARGET: \"14.0\"\n    OUTPUT_SKU: \"osx-14.0-arm64\"\n    FORCE_PYTHON_PLATFORM: macosx_14_0_arm64\n\n\n# ======================================\n#\n#     Build Lib Windows\n#\n# ======================================\n\n\n.build-win-common:\n  stage: build\n  needs:\n    - \"prepare_version\"\n  retry: 1\n  variables:\n    # We need to override arrow's boost 1.85.0 requirement to match the version we use internally.\n    EXTRA_INSTALL_ARGS: \"-o arrow:with_thrift=False -o arrow:parquet=False --require=boost/1.86.0@ -o boost:without_locale=True\"\n  before_script:\n    - uv venv .venv_conan --python ${CONAN_VENV_PYTHON} --seed\n    - source .venv_conan/Scripts/activate\n    - uv pip install 'conan<2' \"cmake==${CMAKE_VERSION}\"\n    - conan config install --verify-ssl=no \"${CONAN_CONFIG_URL}\"\n    - conan_exe=$(which conan)\n    - uv python install ${PYTHON_VERSION}\n    - uv venv --python \"python${PYTHON_VERSION}\" .venv --seed\n    - source .venv/Scripts/activate\n  script:\n    - uv pip install build\n    - !reference [\".conan-build-and-test\"]\n  after_script:\n    # HACK: for some reason, pod5_unit_tests.exe is sticking around; deleting it works, but it\n    # doesn't go away immediately (as though something had it open with FILE_SHARE_DELETE, although\n    # the Handle utility from SysInternals couldn't find anything).\n    # This also appears to be happening for the fuzz targets, so remove and wait for every exe.\n    - rm -v build/Release/bin/*.exe\n    - date\n    - while true; do\n    -   ls build/Release/bin/*.exe || break\n    -   sleep 1\n    - done\n    - date\n\nwin-x64-msvc2019-release-build:\n  extends:\n    - .build-win-common\n    - .parallel-py-versions\n  tags:\n    - windows\n    - VS2019\n    - conan\n  variables:\n    CONAN_PROFILE: \"windows-x86_64-vs2019.jinja\"\n    CONAN_PROFILE_CPPSTD: 17\n    OUTPUT_SKU: \"win-x64\"\n    CMAKE_ARGS: \"-A x64\"\n    CMAKE_GENERATOR: \"Visual Studio 16 2019\"\n  artifacts:\n    name: \"${CI_JOB_NAME}-artifacts\"\n    paths:\n      - \"lib_pod5*.tar.gz\"\n      - \"lib_pod5*.whl\"\n\n# ======================================\n#\n#     Build Python API\n#\n# ======================================\n\n\nbuild-python-api:\n  stage: build\n  needs:\n    - \"prepare_version\"\n  image: ${CI_REGISTRY}/traque/ont-docker-base/ont-base-python:3.10\n  tags:\n    - linux\n  script:\n    - git tag -d $(git tag -l \"*a*\")\n    - git tag -d $(git tag -l \"*b*\")\n    - git tag -d $(git tag -l \"*r*\")\n    - git tag -d $(git tag -l \"*c*\")\n    - git tag -d $(git tag -l \"*dev*\")\n    - if [[ ${CI_COMMIT_TAG/#v/} && -z $( git tag -l \"${CI_COMMIT_TAG/#v/}\" ) ]]; then git tag ${CI_COMMIT_TAG/#v/}; fi\n    - cat _version.py\n    - VERSION=$(grep \"__version__\" _version.py | awk '{print $5}' | tr -d \"'\" | cut -d'+' -f1)\n    - echo $VERSION\n    - cd python/pod5/\n    # update the lib_pod5 dependency in pod5/pyproject.toml to match\n    - sed -i \"s/.*lib_pod5.*/\\ \\ \\ \\ \\'lib_pod5 == ${VERSION}\\',/\" pyproject.toml\n    - cat pyproject.toml\n    - pip install -U pip build\n    - python -m build --outdir ../../\n    - cd ../..\n    - ls *.whl *.tar.gz\n  artifacts:\n    name: \"${CI_JOB_NAME}-artifacts\"\n    paths:\n      - \"pod5*.whl\"\n      - \"pod5*.tar.gz\"\n\n\n# ======================================\n#\n#     Test Tools\n#\n# ======================================\n\ntools-linux-x64:\n  extends:\n    - .parallel-py-versions\n  stage: test\n  image: ${CI_REGISTRY}/traque/ont-docker-base/ont-base-python:${PYTHON_VERSION}\n  tags:\n    - linux\n  before_script:\n    - python${PYTHON_VERSION} -m venv .venv/\n    - source .venv/bin/activate\n  needs:\n    - linux-x64-gcc9-release-build\n    - build-python-api\n  script:\n    - pip install ./lib_pod5*cp${PYTHON_VERSION/./}*.whl pod5-*.whl\n    - pod5 convert fast5 ./test_data/ --output ./output_files --one-to-one ./test_data\n    - python${PYTHON_VERSION} python/pod5/test_utils/check_pod5_files_equal.py ./output_files/multi_fast5_zip.pod5 ./test_data/multi_fast5_zip_v4.pod5\n    - python${PYTHON_VERSION} python/pod5/test_utils/check_pod5_files_equal.py ./output_files/multi_fast5_zip.pod5 ./test_data/multi_fast5_zip_v3.pod5\n    - python${PYTHON_VERSION} python/pod5/test_utils/check_pod5_files_equal.py ./output_files/multi_fast5_zip.pod5 ./test_data/multi_fast5_zip_v2.pod5\n    - python${PYTHON_VERSION} python/pod5/test_utils/check_pod5_files_equal.py ./output_files/multi_fast5_zip.pod5 ./test_data/multi_fast5_zip_v1.pod5\n    - python${PYTHON_VERSION} python/pod5/test_utils/check_pod5_files_equal.py ./output_files/multi_fast5_zip.pod5 ./test_data/multi_fast5_zip_v0.pod5\n    - pod5 convert to_fast5 ./output_files/ --output ./output_files\n    - pod5 convert fast5 ./output_files/*.fast5 --output ./output_files_2 --one-to-one ./output_files/\n    - python${PYTHON_VERSION} python/pod5/test_utils/check_pod5_files_equal.py ./output_files/multi_fast5_zip.pod5 ./output_files_2/*.pod5\n\n\n# ======================================\n#\n#     Pytest\n#\n# ======================================\n\n\n.pytest:\n  stage: test\n  before_script:\n    - python${PYTHON_VERSION} -m venv .venv/\n    - source .venv/*/activate\n    - python --version\n    - python -m pip install --upgrade pip\n  script:\n    - pip install ./lib_pod5*cp${PYTHON_VERSION/./}*.whl pod5-*.whl\n    - pip install pytest pytest-cov pytest-mock psutil\n    - pytest\n    - POD5_DISABLE_MMAP_OPEN=1 pytest\n\n.pytest-with-uv:\n  extends:\n    - .pytest\n  before_script:\n    - uv python install ${PYTHON_VERSION}\n    - uv venv --python \"python${PYTHON_VERSION}\" .venv --seed\n    - source .venv/*/activate\n\npytest-linux-x64:\n  extends:\n    - .pytest\n    - .parallel-py-versions\n  image: ${CI_REGISTRY}/traque/ont-docker-base/ont-base-python:${PYTHON_VERSION}\n  tags:\n    - linux\n  needs:\n    - linux-x64-gcc9-release-build\n    - build-python-api\n\npytest-linux-aarch64:\n  extends:\n    - .pytest\n    - .parallel-py-versions\n  image: ${CI_REGISTRY}/traque/ont-docker-base/ont-base-python:${PYTHON_VERSION}\n  tags:\n    - linux_aarch64\n    - high-cpu\n  needs:\n    - linux-aarch64-gcc9-release-build\n    - build-python-api\n\npytest-osx-arm64:\n  extends:\n    - .pytest-with-uv\n    - .parallel-py-versions\n  tags:\n    - osx_arm64\n  needs:\n    - osx-arm64-clang15-release-build\n    - build-python-api\n\npytest-win-x64:\n  retry: 1\n  extends:\n    - .pytest-with-uv\n    - .parallel-py-versions\n  tags:\n    - windows\n  needs:\n    - win-x64-msvc2019-release-build\n    - build-python-api\n\n\n# ======================================\n#\n#     Conda Testing\n#\n# ======================================\n\n\nconda_pytest:\n  extends:\n    - .pytest\n    - .parallel-py-versions\n  image: ${CI_REGISTRY}/minknow/pod5-file-format/conda:latest\n  tags:\n    - linux\n  needs:\n    - linux-x64-gcc9-release-build\n    - build-python-api\n  before_script:\n    - |\n      cat > environment.yml << EOF\n      name: pod5_conda_test\n      channels:\n        - conda-forge\n        - bioconda\n      dependencies:\n        - python=${PYTHON_VERSION}\n        - cmake\n        - pyarrow\n        - pip\n      EOF\n    - cat environment.yml\n    - mamba --version\n    - mamba env create -f environment.yml\n    - conda env list\n    # This is a work around for conda init in gitlab\n    - eval \"$(conda shell.bash hook)\"\n    - conda activate pod5_conda_test\n\n\n# ======================================\n#\n#     Benchmarks\n#\n# ======================================\n\n\n.benchmark:\n  stage: test\n  before_script:\n    - python3 -m venv .venv/\n    - source .venv/bin/activate\n  script:\n    - pip install ./${LIB_WHEEL_GLOB} pod5-*.whl setuptools\n    - pip install -r ./benchmarks/image/requirements-benchmarks.txt\n    - ./benchmarks/image/install_slow5.sh\n    - export PATH=\"$(pwd)/slow5tools-v1.3.0/:$PATH\"\n    - ./benchmarks/run_benchmarks.py ./test_data/ ./benchmark-outputs\n\nbenchmark-linux-x64:\n  extends: [\".benchmark\"]\n  image: ${CI_REGISTRY}/traque/ont-docker-base/ont-base-python:3.14\n  tags:\n    - linux\n  needs:\n    - linux-x64-gcc9-release-build\n    - build-python-api\n  variables:\n    LIB_WHEEL_GLOB: \"lib_pod5*cp314*.whl\"\n\nbenchmark-linux-aarch64:\n  extends: [\".benchmark\"]\n  image: ${CI_REGISTRY}/traque/ont-docker-base/ont-base-python:3.14\n  tags:\n    - linux_aarch64\n    - high-cpu\n  needs:\n    - linux-aarch64-gcc9-release-build\n    - build-python-api\n  variables:\n    LIB_WHEEL_GLOB: \"lib_pod5*cp314*.whl\"\n\n\n# ======================================\n#\n#     Fuzz tests and coverage reports\n#\n# ======================================\n\n.generic-linux-x64-gcc11-build:\n  stage: build\n  image: external-docker.artifactory.oxfordnanolabs.local/ubuntu:jammy\n  tags:\n    - linux\n  variables:\n    CONAN_PROFILE: \"linux-x86_64-gcc11.jinja\"\n    CONAN_PROFILE_CPPSTD: 20\n    CMAKE_BUILD_TYPE: Release\n  needs:\n    - \"prepare_version\"\n  script:\n    # Install requirements.\n    - apt-get update\n    - apt-get install -y pip\n    - pip install -U pip 'conan<2' auditwheel build \"cmake==${CMAKE_VERSION}\"\n    - conan config install --verify-ssl=no ${CONAN_CONFIG_URL}\n    # Setup build.\n    - pod5_version=\"$(cmake -P ci/get_tag_version.cmake 2>&1)\"\n    - mkdir -p build\n    - pushd build\n        # Tell conan that it's OK to use libstdc++ settings.\n    -   conan install\n          --profile ${CONAN_PROFILE}\n          -s compiler.libcxx=libstdc++11\n          -s compiler.cppstd=${CONAN_PROFILE_CPPSTD}\n          -s build_type=${CMAKE_BUILD_TYPE}\n          -o arrow:with_boost=False\n          -o arrow:with_thrift=False\n          -o arrow:parquet=False\n          ..\n    -   cmake\n          -D CMAKE_TOOLCHAIN_FILE=build/generators/conan_toolchain.cmake\n          -D CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}\n          -D BUILD_PYTHON_WHEEL=OFF\n          ${CMAKE_EXTRA_ARGS}\n          ..\n        # Do the build\n    -   cmake --build . --config ${CMAKE_BUILD_TYPE} --parallel\n    - popd\n\nlinux-x64-gcc11-fuzz:\n  extends: .generic-linux-x64-gcc11-build\n  allow_failure: true\n  variables:\n    CC: clang\n    CXX: clang++\n    CMAKE_EXTRA_ARGS: \"-D ENABLE_FUZZERS=ON -D FUZZER_RUN_TIME=600\"\n  script:\n    # We need clang for libFuzzer.\n    - apt-get update\n    - apt-get install -y clang\n    # Do the build.\n    - !reference [\".generic-linux-x64-gcc11-build\", \"script\"]\n    # Remove the zipped corpora now that we've extracted it, since we\n    # don't need it artifacted.\n    - rm fuzz/*.zip\n    # Run the tests.\n    - ctest -C Release --test-dir build -VV -R ${FUZZER_TEST}\n  parallel:\n    matrix:\n      - FUZZER_TEST:\n        - \"compress\"\n        - \"file\"\n  artifacts:\n    # Artifact everything in /fuzz so that we can get to any new/failing corpora.\n    when: always\n    paths:\n      - ./fuzz\n\nlinux-x64-gcc11-coverage:\n  extends: .generic-linux-x64-gcc11-build\n  variables:\n    CMAKE_BUILD_TYPE: \"Debug\"\n    CMAKE_EXTRA_ARGS: \"-D POD5_DISABLE_TESTS=OFF -D ENABLE_COVERAGE_REPORT=ON\"\n  script:\n    # We need a venv.\n    - apt-get update\n    - apt-get install -y python3-venv\n    # Do the build.\n    - !reference [\".generic-linux-x64-gcc11-build\", \"script\"]\n    # Run the coverage report.\n    - ./ci/generate_coverage_report.sh build\n  coverage: '/^TOTAL\\s+\\d+\\s+\\d+\\s+(\\d+(?:\\.\\d+)?\\%)$/'\n  artifacts:\n    reports:\n      coverage_report:\n        coverage_format: cobertura\n        path: coverage-report-*.xml\n    paths:\n      # Artifact the human readable ones too.\n      - coverage-report-*.html\n\n\n# ======================================\n#\n#     Conan\n#\n# ======================================\n\n.setup-venv:\n  - KERNEL=$(uname -s)\n  - if [[ ! ${KERNEL} =~ \"Linux\" ]]; then\n      # Must use an explicit version here otherwise we get the windows store one.\n      # Can be any version since it's only for installing conan.\n  -   python3.13 -m venv .venv\n  -   source .venv/*/activate\n  - fi\n\n.reset-line-endings:\n  # This is needed to enforce LF line-endings in the pybind submodule\n  # otherwise conan generates different revisions for windows and unix\n  - re='^(MINGW|CYGWIN|MSYS).*'\n  - if [[ $(uname -s) =~ $re ]]; then\n  -   git rm -rf :/\n  -   git checkout HEAD -- :/\n  - fi\n\n.conan-setup-common:\n  - !reference [\".reset-line-endings\"]\n  - !reference [\".setup-venv\"]\n  - pip install 'conan<2'\n  - conan --version\n  - VERSIONS=\"$(cmake -P ci/get_tag_version.cmake 2>&1)\"\n\n.conan-build-common:\n  stage: build-conan\n  dependencies:\n    - \"prepare_version\"\n  before_script:\n    - !reference [\".conan-setup-common\"]\n    - conan remove \"*\" -f\n    - conan config install --verify-ssl=no \"${CONAN_CONFIG_URL}\"\n\n.conan2-common:\n  before_script:\n    - !reference [\".reset-line-endings\"]\n    - !reference [\".setup-venv\"]\n    - pip install --upgrade conan\n    - conan --version\n    - conan remove \"*\" --confirm\n    - conan config install --verify-ssl=no \"${CONAN2_CONFIG_URL}\"\n\n.conan2-build:\n  extends: .conan2-common\n  stage: build-conan\n  dependencies:\n    - \"prepare_version\"\n  script:\n    - version=$(cmake -P ci/get_tag_version.cmake 2>&1 | cut -d. -f1-3)\n      # set up the correct ref\n    - opts=(\"--version=${version}\" --user=nanopore --channel=stable)\n      # fail if we can't find dependencies\n    - opts+=(\"--build=pod5_file_format/*\")\n      # select the build profile\n    - opts+=(-pr:a \"${PROFILE_BASE}\")\n      # use the arrow packages we have built\n    - opts+=('-o:a=arrow/*:with_thrift=False' '-o:a=arrow/*:parquet=False' '-o:a=arrow/*:with_zstd=True' '-o:a=arrow/*:with_boost=False')\n    - echo \"Running conan create . ${opts[@]}\"\n    - conan create . \"${opts[@]}\"\n    - conan cache save \"*/*:*\" --file=conan-${CI_JOB_ID}.tgz\n  variables:\n    # use an arrow package that doesn't use Boost, even on Windows\n    CONAN_MANUAL_OVERRIDES: \"arrow/*:arrow/18.0.0@nanopore/noboost\"\n    CONAN_PROFILE_CPPSTD: \"20\"\n  artifacts:\n    paths:\n      - 'conan-*.tgz'\n  parallel:\n    matrix:\n      - CONAN_PROFILE_BUILD_TYPE: [\"Debug\", \"Release\"]\n\n.conan2-upload:\n  extends: .conan2-common\n  stage: deploy\n  #only: [\"tags\"]\n  script:\n    - for f in conan-*.tgz; do conan cache restore \"$f\"; done\n    - conan remote auth ONT-Conan-V2 --force\n    - conan upload \"*:*\" --check --confirm --remote=ONT-Conan-V2 --dry-run\n\n.conan-upload:\n  extends: .upload-package # from informatics/conan-config\n  stage: deploy\n  only: [\"tags\"]\n  before_script:\n    - pip install \"cmake==${CMAKE_VERSION}\"\n    - !reference [\".conan-setup-common\"]\n  variables:\n      EXPECTED_PACKAGE_COUNT: \"4\" # Expect shared and static packages\n\n# Conan: build and upload packages:\nbuild-conan:windows-x86_64-vs2019:\n    extends:\n        - .profile-windows-x86_64-vs2019\n        - .build-package-win\n        - .conan-build-common\n        - .build-shared-and-static\n    needs: [\"prepare_version\", \"win-x64-msvc2019-release-build\"]\nupload-conan:windows-x86_64-vs2019:\n    extends: .conan-upload\n    dependencies: [ \"prepare_version\", \"build-conan:windows-x86_64-vs2019\" ]\n\nbuild-conan2:windows-x86_64-vs2019:\n    extends:\n        - .conan2-build\n        - .profile-windows-x86_64-vs2019-conan2\n    needs: [\"prepare_version\", \"win-x64-msvc2019-release-build\"]\nupload-conan2:windows-x86_64-vs2019:\n    extends:\n        - .conan2-upload\n        - .profile-windows-x86_64-vs2019-conan2\n    dependencies: [ \"prepare_version\", \"build-conan2:windows-x86_64-vs2019\" ]\n\nbuild-conan:macos-aarch64-appleclang-15.0:\n    extends:\n        - .profile-macos-aarch64-appleclang-15.0\n        - .build-package\n        - .conan-build-common\n        - .build-shared-and-static\n    needs: [\"prepare_version\", \"osx-arm64-clang15-release-build\"]\nupload-conan:macos-aarch64-appleclang-15.0:\n    extends: .conan-upload\n    dependencies: [ \"prepare_version\", \"build-conan:macos-aarch64-appleclang-15.0\" ]\n\nbuild-conan:macos-aarch64-appleclang-16.0:\n    extends:\n        - .profile-macos-aarch64-appleclang-16.0\n        - .build-package\n        - .conan-build-common\n        - .build-shared-and-static\n    needs: [\"prepare_version\"]\nupload-conan:macos-aarch64-appleclang-16.0:\n    extends: .conan-upload\n    dependencies: [ \"prepare_version\", \"build-conan:macos-aarch64-appleclang-16.0\" ]\n\nbuild-conan2:macos-aarch64-appleclang-15.0:\n    extends:\n        - .profile-macos-aarch64-appleclang-15.0-conan2\n        - .conan2-build\n    needs: [\"prepare_version\", \"osx-arm64-clang15-release-build\"]\nupload-conan2:macos-aarch64-appleclang-15.0:\n    extends:\n      - .profile-macos-aarch64-appleclang-15.0-conan2\n      - .conan2-upload\n    dependencies: [ \"prepare_version\", \"build-conan2:macos-aarch64-appleclang-15.0\" ]\n\nbuild-conan2:macos-aarch64-appleclang-16.0:\n    extends:\n        - .profile-macos-aarch64-appleclang-16.0-conan2\n        - .conan2-build\n    needs: [\"prepare_version\"]\nupload-conan2:macos-aarch64-appleclang-16.0:\n    extends:\n        - .profile-macos-aarch64-appleclang-16.0-conan2\n        - .conan2-upload\n    dependencies: [ \"prepare_version\", \"build-conan2:macos-aarch64-appleclang-16.0\" ]\n\nbuild-conan:linux-x86_64-gcc11:\n    extends:\n        - .profile-linux-x86_64-gcc11\n        - .build-package\n        - .conan-build-common\n        - .build-shared-and-static\n    needs: [\"prepare_version\"]\nupload-conan:linux-x86_64-gcc11:\n    extends: .conan-upload\n    dependencies: [ \"prepare_version\", \"build-conan:linux-x86_64-gcc11\" ]\n\nbuild-conan2:linux-x86_64-gcc11:\n    extends:\n        - .profile-linux-x86_64-gcc11-conan2\n        - .conan2-build\n    needs: [\"prepare_version\"]\nupload-conan2:linux-x86_64-gcc11:\n    extends:\n        - .profile-linux-x86_64-gcc11-conan2\n        - .conan2-upload\n    dependencies: [ \"prepare_version\", \"build-conan2:linux-x86_64-gcc11\" ]\n\nbuild-conan2:linux-x86_64-gcc11-asan-static:\n    extends:\n        - .profile-linux-x86_64-gcc11-asan-static-conan2\n        - .conan2-build\n    needs: [\"prepare_version\"]\nupload-conan2:linux-x86_64-gcc11-asan-static:\n    extends:\n        - .profile-linux-x86_64-gcc11-asan-static-conan2\n        - .conan2-upload\n    dependencies: [ \"prepare_version\", \"build-conan2:linux-x86_64-gcc11-asan-static\" ]\n\nbuild-conan2:linux-x86_64-gcc11-usan-static:\n    extends:\n        - .profile-linux-x86_64-gcc11-usan-static-conan2\n        - .conan2-build\n    needs: [\"prepare_version\"]\nupload-conan2:linux-x86_64-gcc11-usan-static:\n    extends:\n        - .profile-linux-x86_64-gcc11-usan-static-conan2\n        - .conan2-upload\n    dependencies: [ \"prepare_version\", \"build-conan2:linux-x86_64-gcc11-usan-static\" ]\n\nbuild-conan2:linux-x86_64-gcc11-tsan-static:\n    extends:\n        - .profile-linux-x86_64-gcc11-tsan-static-conan2\n        - .conan2-build\n    needs: [\"prepare_version\"]\nupload-conan2:linux-x86_64-gcc11-tsan-static:\n    extends:\n        - .profile-linux-x86_64-gcc11-tsan-static-conan2\n        - .conan2-upload\n    dependencies: [ \"prepare_version\", \"build-conan2:linux-x86_64-gcc11-tsan-static\" ]\n\nbuild-conan:linux-x86_64-gcc13:\n    extends:\n        - .profile-linux-x86_64-gcc13\n        - .build-package\n        - .conan-build-common\n        - .build-shared-and-static\n    needs: [\"prepare_version\"]\nupload-conan:linux-x86_64-gcc13:\n    extends: .conan-upload\n    dependencies: [ \"prepare_version\", \"build-conan:linux-x86_64-gcc13\" ]\n\nbuild-conan2:linux-x86_64-gcc13:\n    extends:\n        - .profile-linux-x86_64-gcc13-conan2\n        - .conan2-build\n    needs: [\"prepare_version\"]\nupload-conan2:linux-x86_64-gcc13:\n    extends:\n        - .profile-linux-x86_64-gcc13-conan2\n        - .conan2-upload\n    dependencies: [ \"prepare_version\", \"build-conan2:linux-x86_64-gcc13\" ]\n\nbuild-conan:linux-x86_64-gcc11-asan-static:\n    extends:\n        - .profile-linux-x86_64-gcc11-asan-static\n        - .build-package\n        - .conan-build-common\n        - .build-shared-and-static\n    needs: [\"prepare_version\"]\nupload-conan:linux-x86_64-gcc11-asan-static:\n    extends: .conan-upload\n    dependencies: [ \"prepare_version\", \"build-conan:linux-x86_64-gcc11-asan-static\" ]\n\nbuild-conan:linux-x86_64-gcc13-asan-static:\n    extends:\n        - .profile-linux-x86_64-gcc13-asan-static\n        - .build-package\n        - .conan-build-common\n        - .build-shared-and-static\n    needs: [\"prepare_version\"]\nupload-conan:linux-x86_64-gcc13-asan-static:\n    extends: .conan-upload\n    dependencies: [ \"prepare_version\", \"build-conan:linux-x86_64-gcc13-asan-static\" ]\n\nbuild-conan:linux-x86_64-gcc11-tsan-static:\n    extends:\n        - .profile-linux-x86_64-gcc11-tsan-static\n        - .build-package\n        - .conan-build-common\n        - .build-shared-and-static\n    needs: [\"prepare_version\"]\nupload-conan:linux-x86_64-gcc11-tsan-static:\n    extends: .conan-upload\n    dependencies: [ \"prepare_version\", \"build-conan:linux-x86_64-gcc11-tsan-static\" ]\n\nbuild-conan:linux-x86_64-gcc13-tsan-static:\n    extends:\n        - .profile-linux-x86_64-gcc13-tsan-static\n        - .build-package\n        - .conan-build-common\n        - .build-shared-and-static\n    needs: [\"prepare_version\"]\nupload-conan:linux-x86_64-gcc13-tsan-static:\n    extends: .conan-upload\n    dependencies: [ \"prepare_version\", \"build-conan:linux-x86_64-gcc13-tsan-static\" ]\n\nbuild-conan:linux-x86_64-gcc11-usan-static:\n    extends:\n        - .profile-linux-x86_64-gcc11-usan-static\n        - .build-package\n        - .conan-build-common\n        - .build-shared-and-static\n    needs: [\"prepare_version\"]\nupload-conan:linux-x86_64-gcc11-usan-static:\n    extends: .conan-upload\n    dependencies: [ \"prepare_version\", \"build-conan:linux-x86_64-gcc11-usan-static\" ]\n\nbuild-conan:linux-x86_64-gcc13-usan-static:\n    extends:\n        - .profile-linux-x86_64-gcc13-usan-static\n        - .build-package\n        - .conan-build-common\n        - .build-shared-and-static\n    needs: [\"prepare_version\"]\nupload-conan:linux-x86_64-gcc13-usan-static:\n    extends: .conan-upload\n    dependencies: [ \"prepare_version\", \"build-conan:linux-x86_64-gcc13-usan-static\" ]\n\nbuild-conan:linux-aarch64-gcc11:\n    extends:\n        - .profile-linux-aarch64-gcc11\n        - .build-package\n        - .conan-build-common\n        - .build-shared-and-static\n    needs: [\"prepare_version\"]\nupload-conan:linux-aarch64-gcc11:\n    extends: .conan-upload\n    dependencies: [ \"prepare_version\", \"build-conan:linux-aarch64-gcc11\" ]\n\nbuild-conan2:linux-aarch64-gcc11:\n    extends:\n        - .profile-linux-aarch64-gcc11-conan2\n        - .conan2-build\n    needs: [\"prepare_version\"]\nupload-conan2:linux-aarch64-gcc11:\n    extends:\n        - .profile-linux-aarch64-gcc11-conan2\n        - .conan2-upload\n    dependencies: [ \"prepare_version\", \"build-conan2:linux-aarch64-gcc11\" ]\n\nbuild-conan:linux-aarch64-gcc13:\n    extends:\n        - .profile-linux-aarch64-gcc13\n        - .build-package\n        - .conan-build-common\n        - .build-shared-and-static\n    needs: [\"prepare_version\"]\nupload-conan:linux-aarch64-gcc13:\n    extends: .conan-upload\n    dependencies: [ \"prepare_version\", \"build-conan:linux-aarch64-gcc13\" ]\n\nbuild-conan2:linux-aarch64-gcc13:\n    extends:\n        - .profile-linux-aarch64-gcc13-conan2\n        - .conan2-build\n    needs: [\"prepare_version\"]\nupload-conan2:linux-aarch64-gcc13:\n    extends:\n        - .profile-linux-aarch64-gcc13-conan2\n        - .conan2-upload\n    dependencies: [ \"prepare_version\", \"build-conan2:linux-aarch64-gcc13\" ]\n\n# ======================================\n#\n#     Archive\n#\n# ======================================\n\n\nbuild-archive:\n  stage: archive\n  needs:\n    - linux-x64-gcc9-release-build\n    - linux-aarch64-gcc9-release-build\n    - osx-arm64-clang15-release-build\n    - win-x64-msvc2019-release-build\n    - build-python-api\n  script:\n    - find .\n  artifacts:\n    name: \"${CI_JOB_NAME}-artifacts\"\n    paths:\n      - ./*.tar.gz\n      - ./*.whl\n\n\n# ======================================\n#\n#     Deploy\n#\n# ======================================\n\n\ninternal_wheel_upload:\n  stage: deploy\n  image: ${UPLOAD_PYTHON_IMAGE}\n  needs:\n    - build-archive\n  script:\n    - ls -lh .\n    - pip install twine\n    - twine upload *.whl pod5*.tar.gz\n  only: [\"tags\"]\n  when: manual\n\nexternal_wheel_upload:\n  stage: deploy\n  image: ${UPLOAD_PYTHON_IMAGE}\n  needs:\n    - build-archive\n  script:\n    - ls -lh .\n    - pip install twine\n    - unset TWINE_REPOSITORY_URL\n    - unset TWINE_CERT\n    - twine upload lib*.whl -u __token__ -p\"${EXTERNAL_LIB_POD5_PYPI_KEY}\"\n    - twine upload pod5*.whl pod5*.tar.gz -u __token__ -p\"${EXTERNAL_POD5_PYPI_KEY}\"\n  only: [\"tags\"]\n  when: manual\n\n\n# ======================================\n#\n#     MLHub Testing\n#\n# ======================================\n\nmlhub:\n  stage: deploy\n  image: ${MLHUB_TRIGGER_IMAGE}\n  needs: [\"build-archive\"]\n  variables:\n    GIT_STRATEGY: none\n  script:\n    - |\n      curl -i --header \"Content-Type: application/json\" \\\n      --request POST \\\n      --data '{\n          \"key\": \"'${MLHUB_TRIGGER_KEY}'\",\n          \"job_name\": \"POD5-CI '${CI_COMMIT_REF_NAME}' - '\"$CI_COMMIT_TITLE\"' \",\n          \"script_parameters\": {\n                \"mode\":\"artifact\",\n                \"source\":\"'${CI_COMMIT_SHA}'\"\n                \"python_ver\":\"'${PYTHON_VERSION}'\"\n              }\n      }' \\\n      ${MLHUB_TRIGGER_URL}\n  when: manual\n  extends:\n    - .parallel-py-versions\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"third_party/pybind11\"]\n\tpath = third_party/pybind11\n\turl = https://github.com/pybind/pybind11.git\n\tbranch = stable\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "# See https://pre-commit.com for more information\n# See https://pre-commit.com/hooks.html for more hooks\nrepos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v5.0.0\n    hooks:\n      - id: trailing-whitespace\n      - id: end-of-file-fixer\n      - id: check-case-conflict\n      - id: check-merge-conflict\n      - id: check-added-large-files\n  - repo: https://github.com/psf/black\n    rev: 25.1.0\n    hooks:\n      - id: black\n  - repo: https://github.com/codespell-project/codespell\n    rev: v2.4.1\n    hooks:\n      - id: codespell\n        exclude: 'third_party/'\n  - repo: https://github.com/PyCQA/flake8\n    rev: 7.2.0\n    hooks:\n      - id: flake8\n        exclude: docs/conf.py\n  - repo: https://github.com/shellcheck-py/shellcheck-py\n    rev: v0.10.0.1\n    hooks:\n      - id: shellcheck\n  - repo: https://github.com/pre-commit/mirrors-clang-format\n    rev: 'v20.1.4'\n    hooks:\n      - id: clang-format\n        exclude: 'third_party/'\n  - repo: https://github.com/pre-commit/mirrors-mypy\n    rev: 'v1.15.0'\n    hooks:\n      - id: mypy\n        files: 'python/pod5/src/'\n        args: [ --check-untyped-defs, --ignore-missing-imports ]\n        additional_dependencies:\n          - types-Deprecated\n          - types-setuptools\n          - types-pytz\n\n# NB: by default, pre-commit only installs the pre-commit hook (\"commit\" stage),\n# but you can tell `pre-commit install` to install other hooks.\n# This set of default stages ensures we don't slow down or break other git operations\n# even if you install hooks for them.\ndefault_stages:\n  - pre-commit\n  - pre-merge-commit\n  - manual\n\n# vi:et:sw=2:sts=2:\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Build all formats\nformats: all\n\n# Set the version of Python and other tools you might need\nbuild:\n   os: ubuntu-20.04\n   tools:\n      python: \"3.10\"\n   jobs:\n      pre_build:\n         - python -c \"import pod5; print(pod5.__version__)\"\n# Build documentation in the docs/ directory with Sphinx\nsphinx:\n   configuration: docs/conf.py\n\n# If using Sphinx, optionally build your docs in additional formats such as PDF\n# formats:\n#    - pdf\n\n# Optionally declare the Python requirements required to build your docs\npython:\n   install:\n      - requirements: docs/requirements.txt\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "<!-- markdownlint-disable MD024 -->\n\n# Changelog\n\nAll notable changes, updates, and fixes to pod5 will be documented here\n\nThe format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [0.3.39]\n\n### Fixed\n\n- Fix python bindings build without Conan.\n\n### Changed\n\n- CI Stabilisation\n\n## [0.3.38]\n\n### Changed\n\n- CI Stabilisation\n\n## [0.3.37]\n\n### Changed\n\n- Use standard file IO to read POD5 header and footer metadata before memory mapping (if not disabled e.g. `POD5_DISABLE_MMAP_OPEN=1`). This should to avoid SIGBUS errors caused by memory mapping file stubs (archive artefacts).\n- Improve file_reader_writer unit-tests robustness\n- Scale number of open input file handles during pod5 subset / filter by the system limits and number of output files.\n\n### Fixed\n\n- Fixed bug where invalid read ids could be passed into pod5 subset via summary table.\n- Fixed bug where invalid read ids in `DatasetReader.reads` selection could return valid read records.\n- Fixed bug in CI where the python venv was not activated resulting in incorrect conan version being used.\n\n## [0.3.36]\n\n### Added\n\n- Added missing licence files.\n\n### Removed\n\n- Removed Python 3.9 and macOS 10.15 support since they're EOL.\n\n## [0.3.35]\n\n### Added\n\n- Python 3.14 support\n\n### Removed\n\n- Removed sphinx-style python docstrings references\n- Removed most documentation as part of migration to <https://software-docs.nanoporetech.com/pod5>\n- Deprecated `--duplicate-ok` argument from pod5 tools - duplicating reads is now always invalid.\n\n### Changed\n\n- Moved filter + subset implementation into C++ for improved performance.\n- Performance improvements to `pod5 view` especially when reading read ids from large files.\n- Updated polars version from \"~=1.20,<1.32\" to \"~= 1.30\"\n- Switch to uv for managing CI python environments\n- Updated to pyarrow 22.0.0\n\n## [0.3.34]\n\n### Added\n\n- `open_pore_level` to `pod5 inspect read`\n\n### Changed\n\n- Fixed migration behaviour on nfs systems, where migrated tables could be left orphaned on disk.\n- Limited polars install version to \"~=1.20,<1.32\" following breaking changes\n- Tidied up how tmp files are named, used a larger set of numbers for naming.\n\n## [0.3.33]\n\n### Added\n\n- Added Conan 2 CI\n\n### Changed\n\n- Reduced virtual memory usage when opening POD5 files by 75%.\n- Python API now memory maps inner tables using the `mmap.mmap` `offset` and `length` arguments directly instead of taking a slice of the whole file.\n\n## [0.3.32]\n\n### Added\n\n- Option to allow users of C++ API to not keep file handles open if required.\n\n### Changed\n\n- Order of `pod5 view` is backwards compatible with 0.3.30, new `open_pore_level` field is at the end of the list.\n\n## [0.3.31]\n\n### Added\n\n- Added new field `open_pore_level`, containing the level of the open pore as tracked by MinKNOW for this channel/well.\n\n### Removed\n\n- Deprecated support for unused read scaling values \"tracked_scaling_scale\", \"tracked_scaling_shift\", \"predicted_scaling_scale\", \"predicted_scaling_shift\", \"num_reads_since_mux_change\" and \"time_since_mux_change\". These will be removed from stored data and writer API in 0.4.0, with accessing API remaining in place until 0.5.0.\n\n## [0.3.30]\n\n### Changed\n\n- Build with sanitization on GCC13\n\n### Removed\n\n- Dropped incorrect sanitized conan jobs.\n\n## [0.3.29]\n\n### Removed\n\n- Dropped support for macOS x86\n\n## [0.3.28]\n\n### Changed\n\n- Additional testing for Linux file access.\n\n## [0.3.27]\n\n### Fixed\n\n- Fixed some crashes when parsing corrupt POD5 files.\n- Fixed missing error handling when the C API is called incorrectly.\n- Fixed and clarified C API thread safety.\n- Fall back to regular IO if direct IO is requested, but file opening fails.\n\n### Removed\n\n- Dropped automated ARM+GCC8 builds.\n\n### Changed\n\n- Bumped polars to next major version (`~= 1.20`).\n\n## [0.3.26]\n\n### Changed\n\n- The read end reason now includes paused - for reads that ended because acquisition was paused.\n\n## [0.3.25]\n\n### Changed\n\n- Python 3.8 wheels are no longer built for Windows or macOS (Python 3.8 is end-of-life).\n- Better error messages and testing of file recovery.\n\n### Added\n\n- Conan pod5 builds with address, thread and undefined behaviour sanitizer support.\n- Added fuzz testing.\n- New option cleanup temporary files after file recovery.\n\n## [0.3.24]\n\n### Changed\n\n- Update to arrow 18 for the cpp library.\n\n### Fixed\n\n- Flush `pod5 view` header to prevent issue on Windows systems where header would not be on top.\n\n## [0.3.23]\n\n### Changed\n\n- Removed use of python `build` when building wheel in cmake.\n\n## [0.3.22]\n\n### Added\n\n- `ArrowTableHandle` `stream` member to store the `BatchFileReader` backend\n- `ArrowTableHandle` `options` argument to pass in `IpcReadOptions`\n- `pod5::default_memory_pool` function which selects an appropriate memory pool even on large page systems.\n\n### Changed\n\n- Refactored Multi-threading in `DatasetReader` to prevent too many open files errors\n- Updated dependency to `pyarrow~=18.0.0` for `python>=3.9`\n- Relaxed h5py python dependency\n\n## [0.3.21]\n\n### Added\n\n- Support for python 3.13.\n\n### Changed\n\n- Removed use of Boost. This does not affect the C interface, but may require changes to\n  consumers of the C++ headers.\n\n## [0.3.20]\n\n### Changed\n\n- Refactored directio writing engine to open up async io support.\n- Fixed Boost version compatibility checking in Conan packages.\n\n## [0.3.19]\n\n### Added\n\n- New end reason for reads terminated due to an analysis configuration change.\n\n### Changed\n\n- Reduced allocations when compressing signal.\n\n### Fixed\n\n- Crash when searching empty file for reads.\n\n## [0.3.18]\n\n### Added\n\n- Ability to disable flushing on batch complete\n- Use new LinuxOutputStream to cache allocations and reduce memory when writing many files.\n\n## [0.3.17]\n\n### Changed\n\n- Move svb headers to correct subdirectory in\n\n## [0.3.16]\n\n### Added\n\n- svb16 headers packaged with pod5\n\n### Changed\n\n- Directio output now writes on batch complete without flushing explicitly.\n\n## [0.3.15]\n\n### Added\n\n- Added new end reasons \"api_request\" and \"device_data_error\" to allow for new read end reasons future minknow versions will generate.\n- Allow directio to specify the chunk size directly.\n\n## [0.3.14]\n\n### Added\n\n- gcc8 builds\n\n## [0.3.13]\n\n### Fixed\n\n- Instability when creating a pod5 writer fails.\n- Issue with directio mode where space is over reserved.\n\n## [0.3.12]\n\n### Fixed\n\n- Fixed issues reading signal from uncompressed pod5 files.\n\n## [0.3.11]\n\n### Added\n\n- Typechecking on `Writer.add_reads` to inform users incorrectly passing `ReadRecords`\n- Compatibility with numpy 2.0.\n\n### Fixed\n\n- `DatasetReader` correctly handles string paths\n\n## [0.3.10]\n\n### Added\n\n- Required pypa project metadata.\n\n### Removed\n\n- Dropped support OSX builds for XCode < 14.2.\n\n## [0.3.9]\n\n### Fixed\n\n- `ReadRecord.to_read()` missing fields\n\n## [0.3.8]\n\n### Fixed\n\n- Conan windows upload jobs failure due to using different line endings.\n\n## [0.3.7]\n\n### Fixed\n\n- CI package uploading to PyPi following [API token migration](https://pypi.org/help/#apitoken).\n- Documentation for some functions.\n- Explicitly sized type in `pod5_vbz_decompress_signal()`.\n- CI execution of tests.\n\n### Changed\n\n- Updated `pre-commit` to `clang-format-17`.\n- Updated Arrow to 12.0.0.\n\n## [0.3.6]\n\n### Fixed\n\n- Polars `ColumnNotFoundError: not_set` introduced by `polars==0.20.0`\n\n## [0.3.5]\n\n### Fixed\n\n- Arrow build flags in conanfile are now configured in the configure() fnc rather than being default options.\n\n## [0.3.4]\n\n### Added\n\n- boost_internal_build flag in conanfile.\n- CI now builds with the above flag turned on.\n\n## [0.3.3]\n\n### Added\n\n- CI for appleclang 14\n- cppstd builds\n\n## [0.3.2]\n\n### Added\n\n- Support for Python 3.12\n\n## [0.3.1] 2023-11-10\n\n### Fixed\n\n- Logging no longer calls `basicConfig` which may unintentionally edit users logging configuration\n\n## [0.3.0] 2023-11-07\n\n### Changed\n\n- Transfers dataframes used in subsetting / filter use categorical fields to reduce memory consumption\n- Polars version increased to `~=0.19`\n- Documentation regarding positional arguments\n- Renamed deprecated `polars.groupby` to `polars.group_by`\n\n### Fixed\n\n- Fixed a bug in the build scripts that prevented iOS and Windows Conan packages from being uploaded.\n- Remove exposed artifactory URL env var from gitlab ci config.\n- `convert to_fast5` writes byte encoded read_ids to match Minkow (was `str`)\n\n### Removed\n\n- Removed python3.7 support\n\n## [0.2.9] 2023-11-02\n\n### Fixed\n\n- Corrected the visibility of dependencies when building pod5 as a shared library.\n\n## [0.2.8] 2023-11-01\n\n### Added\n\n- Added compression status to `pod5 inspect summary <file>`\n- Added environment override \"POD5_DISABLE_MMAP_OPEN\" to force non-mmapped opening of files.\n\n### Fixed\n\n- Remove exposed artifactory URL env var from gitlab ci config.\n- `convert to_fast5` writes byte encoded read_ids to match Minkow (was `str`)\n\n## [0.2.7] 2023-09-11\n\n### Added\n\n- `DatasetReader` class for reading collections of pod5 files\n- Return index errors when querying invalid errors from API's\n\n### Changed\n\n- Recursive search for files now traverses symbolic links and ignores hidden files\n- Tweak block size of directio writes to 1MB.\n\n## [0.2.6] 2023-09-04\n\n### Changed\n\n- Write pod5 files using DirectIO on Linux platforms (performance)\n\n## [0.2.5] 2023-08-01\n\n### Added\n\n- Shared builds to conan\n\n### Fixed\n\n- `num_minknow_events` field description from `int8` to `uint64`\n- `ReadRecord.num_minknow_events` return type-hint from `float` to `int`\n\n## [0.2.4] 2023-07-13\n\n### Changed\n\n- Increased `numpy` minimum version to `>= 1.21.0`\n- Improved performance of `subset`, `filter` and `merge` tools.\n- `Repacker.wait` and `Repacker.waiter` parameters\n\n### Deprecated\n\n- `Repacker.wait` and `Repacker.waiter` some parameters are deprecated and issue `FutureWarning`\n\n### Fixed\n\n- `Repacker.is_complete` returning `True` when work is queued.\n\n## [0.2.3] 2023-06-26\n\n### Added\n\n- Add API (pod5_open_file_options) to prevent pod5 from opening a file using mmap, instead using direct file IO.\n- Default field values (empty string) when converting fast5 files with missing fields\n\n### Changed\n\n- Corrected Oxford Nanopore Technologies company name in package metadata to use Public Limited Company (Plc) instead of Limited (Ltd)\n- Limited the number of processes created when specifying `--threads` to the number of cpu cores available `os.cpu_count()`\n- Reduced the default value for `--threads` from 8 to 4 to improve stability on resource constrained systems\n\n## [0.2.2] 2023-06-06\n\n### Fixed\n\n- Add API error when adding reads with invalid end reason, pore type or run info.\n\n## [0.2.1] 2023-05-25\n\n### Changed\n\n- Update internal arrow lib to not export flatbuffers symbols.\n\n## [0.2.0] 2023-05-18\n\n### Added\n\n- `pod5 view` tool to view / inspect pod5 files as tables. Gives a >200x speed improvement compared to `pod5 inspect reads`\n- `pod5 recover` tool to recover data from corrupted / truncated pod5 files\n- `pod5 update` documentation\n- source distributions to pypi\n\n### Changed\n\n- `pod5 subset` and `pod5 filter` uses `polars` to parse inputs\n- `pod5 subset` and `pod5 filter` csv formatting requirements tightened\n- `pod5` tools which use multiple pod5 file inputs now accept directories which can be searched recursively with `-r/--recursive`\n- `pod5 subset` `--read-id-column` argument abbreviateion `-r` change to `-R` to allow `-r/--recursive` to be consistent for all tools\n- `pod5` tools use hyphens in all arguments (e.g. `--force-overwrite` and `--read-id-column`)\n- `pod5 merge` and `pod5 update` uses named `-o/--output` argument instead of positional `output` argument to standardise tools\n- `pod5 update` progress bar and better detection of name conflicts\n- Minimised number of open file handles in tools to prevent `Too many open files` error\n- Logging added to `merge`, `filter` and `subset`. Enabled with `POD5_DEBUG=1`\n\n### Deprecated\n\n- `pod5 inspect reads` deprecated in-favour of `pod5 view`\n\n### Fixed\n\n- Exception raised when calling `pod5` without any arguments\n- Exception raised in `pod5 convert fast5` where closed writers were reopened after being closed by a caught exception\n- Fixed Gitlab 38, pod5_get_end_reason and pod5_get_pore_type ignoring input string length checks.\n\n### Removed\n\n- `pod5 subset` `--json` mapping arguments\n- `pod5 merge` `--chunk-size` argument\n- `ReadTableVersion` replaced with an integer value\n\n## [0.1.21] 2023-04-27\n\n### Fixed\n\n- Repacker `reads_completed` value while copying a selection of reads.\n- Fixed crash when trying to load files with a bad footer.\n\n## [0.1.20] 2023-04-20\n\n### Fixed\n\n- Fixed merging many files running out the size limit of dictionary indices.\n\n## [0.1.19] 2023-04-14\n\n### Changed\n\n- `pod5 convert fast5` now creates logs when `POD5_DEBUG=1` set\n- `pod5 convert fast5` checks multi-read fast5s at conversion time\n\n### Fixed\n\n- Fixed memory usage growth over time as signal was loaded with large pod5 files.\n- Fixed crash loading malicious files (found via fuzz testing)\n- Fixed leaks and UB when running unit tests.\n- Fixed run-away memory consumption during fast5 conversion\n\n## [0.1.17] 2023-04-06\n\n### Changed\n\n- Updated internal arrow version to 8.0.0.3\n\n## [0.1.16] 2023-04-06\n\n### Fixed\n\n- Fixed issue where pod5 would read out of bounds memory when decompressing some reads.\n\n## [0.1.15] 2023-03-31\n\n### Changed\n\n- Refactored `pod5 convert fast5` to use `concurrent.futures` only.\n- Add further info to error message when signal cannot be decompressed by zstd\n- Make merge operation not generate multiple identical run infos.\n\n### Fixed\n\n- Fixed closing uninitialised file handles.\n- Fixed `pod5 inspect reads` repeating header\n- Fixed a crash with certain pod5 search operations.\n\n## [0.1.13] 2023-03-23\n\n### Fixed\n\n- Fix loading large pod5 files on virtual-memory limited systems.\n\n## [0.1.12] 2023-03-20\n\n### Added\n\n- Added `--output` argument to `pod5 convert fast5` and `to_fast5` replacing positional argument of the same name\n- Added `--strict` argument to `pod5 convert fast5` to promptly stop on exceptions\n- Added readthedocs documentation links in README.md\n\n### Changed\n\n- Updated developer installation instructions to use `conan<2`\n- Reworked `pod5 convert fast5` to tolerate runtime exceptions\n- Use same type `run_info_index_t` for `pod5_get_file_run_info_count` and `pod5_get_file_run_info`.\n\n### Fixed\n\n- Fixed file handle leak in repacker\n\n## [0.1.11] 2023-03-13\n\n### Added\n\n- Python API supports python 3.11\n- Added missing python API wheels on windows\n\n### Changed\n\n- Changed python API dependency version `pyarrow~=11.0.0` from `8.0.0` to support python 3.11\n- Changed python API dependency version `hdf5~=8.0.0` from `v7.0.0` to support python 3.11\n\n## [0.1.10] 2023-03-09\n\n### Added\n\n- Added `pod5_get_read_count` to find the count of all reads in file\n- Added `pod5_get_read_ids` to retrieve all read id's in file\n- Added `pod5_get_file_run_info` to retrieve a run info at an absolute index in the file\n- Added `pod5_free_run_info` to free run info's (replaces `pod5_release_run_info`)\n- Added `pod5_get_file_run_info_count` to find the number of run info's in a file\n- Added `pod5 filter` tool to subset pod5 files with simple list of read ids\n- Added `tqdm` progress bar to `pod5 subset` (disable with `POD5_PBAR=0`)\n\n### Changed\n\n- Reworked `pod5 subset` to give better control over resources used\n- `pod5 subset` can now parse csv and tsv tables / summaries\n- `pod5 repack` now repacks all inputs one-to-one\n\n### Deprecated\n\n- Deprecated `pod5_release_run_info` (see `pod5_free_run_info`)\n\n### Removed\n\n- Removed filepath header line from `pod5 inspect reads`\n\n## [0.1.9] 2023-03-07\n\n### Added\n\n- Added version attributes to `lib-pod5`\n\n### Changed\n\n- Versioning now controlled by VCS inspection using `setuptools_scm`\n\n## [0.1.8] 2023-02-23\n\n### Added\n\n- Added more `read_id` getter methods to `Reader`\n- Added support for python 3.8 + 3.10 on windows\n- Added gcc7 linux build of pod5\n\n### Changed\n\n- Update to zlib 1.2.13\n- Update to zstd 1.5.4\n- Pinned `pre-commit=v2.21.0` while supporting `python3.7`\n- Reworked `pod5 convert to_fast5` output filenames to allow for `1-1` mapping\n\n### Fixed\n\n- Fixed `pod5 inspect read`\n- Fixed `pod5 convert to_fast5` creating an empty fast5 output\n- Fixed `pod5 convert to_fast5` ignoring the `--force_overwrite` argument\n- Fixed issue where thread_pool.h wasn't shipped.\n\n## [0.1.5] - 2023-01-20\n\n### Added\n\n- Explicitly re-exported `lib-pod5` public symbols and added `py.typed` marker file to support type-checking.\n\n### Fixed\n\n- Fixed issue where closing many pod5 files in sequence is slow.\n- Fixed incorrect python types and adopted python type-checking.\n\n## [0.1.4] - 2022-12-22\n\n### Added\n\n- Linux python 3.11 wheels\n- ReadTheDocs documentation support\n\n### Fixed\n\n- OSX arm64 wheel naming corrections - works with wider set of python executables\n\n## [0.1.3] - 2022-12-16\n\n### Added\n\n- Added `Reader.__iter__` method.\n\n### Changed\n\n- Renamed `EndReason.name` to `EndReason.reason` to access the inner enum and added\n  `EndReason.name` as a property to return the string representation of this enum value.\n- `BaseRead`, `Read`, `CompressedRead`, `Calibration` and `Pore` dataclasses are now mutable.\n\n### Removed\n\n- Removed deprecated `Writer` functions.\n\n### Fixed\n\n- Fixed osx arm64 wheel compatibility for older python versions.\n- Fixed EndReason type errors.\n- Fixed EndReason in pod5 to fast5 conversion.\n\n## [0.1.2] - 2022-12-06\n\n### Changed\n\n- Optimised the file writing utilities\n\n## [0.1.1] - 2022-12-06\n\n### Changed\n\n- Restricted exported boost dependencies of conan package to just the boost::headers component.\n\n## [0.1] - 2022-12-02\n\n### Changed\n\n- Documentation edits\n- `Writer.add_reads` now handles both `Read` and `CompressedRead`.\n\n### Deprecated\n\n- Deprecated `Writer` methods `add_read_object` and `add_read_objects` for `add_read` and `add_reads` respectively.\n\n### Removed\n\n- Removed direct pod5 tool scripts.\n\n### Fixed\n\n- Fixed name of internal utils - \"pad_file\".\n- Fixed spelling of various internal variables.\n- Fixed `pod5 convert to_fast5`\n\n## [0.0.43]\n\n### Changed\n\n- Reformat c++ code with more consistent format file.\n\n## [0.0.42]\n\n### Added\n\n- Added `pod5` tools entry-point\n- Added api to query file version information as written on disk.\n\n### Changed\n\n- Fixed signal_chunk_size type error in convert-from-fast5\n- Replaced `ont_fast5_api` dependency with `vbz_h5py_plugin`\n- Restructured Python packaging to include `lib_pod5_format` which contains the native bindings build from pybind11.\n- `pod5_format` and `pod5_format_tools` are now pure python packages which depend on `lib_pod5_format`\n- Python packages `pod5_format` and `pod5_format_tools` have been merged into single `pod5` pure-python package.\n- `pod5-convert-from-fast5` `--output-one-to-one` reworked so that output files maintain the input structure making this argument more flexible and avoid filename clobbering.\n- Added missing `lib_pod5.update_file` function to pyi.\n- `pod5-convert-from-fast5` `output` now takes existing directories and\n  writes `output.pod5` (current behaviour) or creates a new file with the given name if it doesn't exist.\n- Renamed arguments in tools relating to multi-processing / multi-threading from `-p/--processes` to the mode common `-t/--threads`.\n\n## [0.0.41] - 2022-10-27\n\n### Changed\n\n- Fixed pod5-inspect erroring when loading data.\n- Fixed issue where some files in between 0.34 - 0.38 wouldn't load correctly.\n\n## [0.0.40] - 2022-10-27\n\n### Changed\n\n- Fixed migrating of large files from older versions.\n\n## [0.0.39] - 2022-10-18\n\n### Changed\n\n- Fixed building against the c++ api - previously missing include files.\n\n## [0.0.38] - 2022-10-18\n\n### Changed\n\n- All data in the read table that was previously contained in dictionaries of structs is now stored in the read table, or a new \"run info\" table.\n  This change simplifies data access into the pod5 files, and helps users who want to convert the pod5 data to pandas or other arrow-compatible reader formats.\n  Old data is migrated on load, and will continue to work, data can be permanently migrated using the tool `pod5-migrate`\n\n### Removed\n\n- Support for opening and writing \"split\" pod5 files. All API's now expect and return combined pod5 files.\n\n## [0.0.37] - 2022-10-18\n\n### Changed\n\n- Updated Conan recipe to support building without specifying C++ standard version.\n\n## [0.0.36] - 2022-10-07\n\n### Changed\n\n- Bump the Boost and Arrow versions to pick up latest changes.\n\n## [0.0.35] - 2022-10-07\n\n### Changed\n\n- Support C++17 + C++20 with the conan package pod5 generates.\n\n## [0.0.34] - 2022-10-05\n\n### Changed\n\n- Modified `pod5_format_tools/pod5_convert_to_fast5.py` to separate `pod5_convert_to_fast5_argparser()` and `convert_from_fast5()` out from `pod5_convert_from_fast5.main()`.\n\n## [0.0.33] - 2022-10-05\n\n### Added\n\n- Added `num_samples` field to read table, containing the total number of samples a read contains. The field is filled in by API if it doesn't exist.\n\n### Changed\n\n- File version is now V2, due to the addition of `num_samples`.\n\n## [0.0.32] - 2022-10-03\n\n### Fixed\n\n- Fixed an issue where multi-threaded access to a single batch could cause a crash discovered by dorado testing.\n- Fixed help text in convert to fast5 script.\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.18.0)\n\nproject(POD5)\n\ninclude(${PROJECT_SOURCE_DIR}/cmake/POD5Version.cmake)\n\nset(CMAKE_PROJECT_VERSION ${POD5_NUMERIC_VERSION})\n\nset(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} \"${PROJECT_SOURCE_DIR}/cmake\")\n\n# use compiler cache if available\noption(DISABLE_CCACHE \"Do not try to use ccache to speed compilation\" NO)\nif (NOT DISABLE_CCACHE)\n    find_program(CCACHE_EXECUTABLE ccache\n        HINTS \"C:/Program\\ Files/ccache/\"\n        )\n    if (CCACHE_EXECUTABLE)\n        message(STATUS \"Using ccache: ${CCACHE_EXECUTABLE}\")\n        set(CMAKE_CXX_COMPILER_LAUNCHER \"${CCACHE_EXECUTABLE}\")\n        set(CMAKE_C_COMPILER_LAUNCHER \"${CCACHE_EXECUTABLE}\")\n    endif()\nendif()\n\nif (NOT DEFINED ENABLE_CONAN)\n    option(ENABLE_CONAN \"Enable conan for dependency installation\" OFF)\nendif()\n\nif (NOT DEFINED CONAN2)\n    option(CONAN2 \"Temp flag until we fully migrate to conan2\" OFF)\nendif()\n\noption(BUILD_SHARED_LIB \"Build a shared library\" OFF)\n\noption(POD5_DISABLE_TESTS \"Disable building all tests\" ON)\noption(POD5_BUILD_EXAMPLES \"Enable building all examples\" OFF)\n\noption(ENABLE_ADDRESS_SANITIZER \"Enable address sanitizer\" OFF)\n\nif (NOT DEFINED ENABLE_POD5_PACKAGING)\n    option(ENABLE_POD5_PACKAGING \"Enable packaging support\" ON)\nendif()\n\noption(BUILD_PYTHON_WHEEL \"Build a python wheel for pod5\" OFF)\n\n# debug symbols don't depend on the build type, only on this option\noption(DISABLE_DEBUG_SYMBOLS \"Force debug symbols to be disabled\" OFF)\nif (NOT DISABLE_DEBUG_SYMBOLS)\n    if (MSVC)\n        # Z7 embeds deubgging info into .obj files, which is easier to manage for\n        # build accelerators (note that a .pdb will still be generated for libs)\n        # https://docs.microsoft.com/en-us/cpp/build/reference/z7-zi-zi-debug-information-format\n        add_compile_options(/Z7)\n        # this will use fastlink in the IDE and full link from the command link\n        # https://docs.microsoft.com/en-us/cpp/build/reference/debug-generate-debug-info\n        set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} /DEBUG\")\n        set(CMAKE_MODULE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} /DEBUG\")\n        set(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} /DEBUG\")\n        # /DEBUG option is not recognised for STATIC lib linking\n    elseif (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES \"Clang\")\n        add_compile_options(-g)\n    endif()\nendif()\n\noption(ENABLE_FUZZERS \"Build the fuzzers that can be used to catch new issues\" OFF)\nif (ENABLE_FUZZERS)\n    include(pod5_fuzz)\nendif()\nadd_compile_definitions(POD5_ENABLE_FUZZERS=$<BOOL:${ENABLE_FUZZERS}>)\n\noption(ENABLE_COVERAGE_REPORT \"Executables emit coverage reports\" OFF)\nif (ENABLE_COVERAGE_REPORT)\n    if (DISABLE_DEBUG_SYMBOLS)\n        message(FATAL_ERROR \"Debug symbols are required for coverage reports to work\")\n    elseif (NOT CMAKE_BUILD_TYPE STREQUAL \"Debug\")\n        message(FATAL_ERROR \"Only unoptimised builds give reliable coverage reports\")\n    elseif (CMAKE_CXX_COMPILER_ID MATCHES \"(GNU|Clang)\")\n        add_compile_options(--coverage)\n        add_link_options(--coverage)\n    else()\n        message(FATAL_ERROR \"Cannot enable coverage on unknown compiler\")\n    endif()\nendif()\n\n\n# FIXME: DISABLE CONDITIONAL TO WORK ON BIONIC\nif (ENABLE_CONAN AND CMAKE_COMPILER_IS_GNUCXX AND\n        CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL \"9.0\" AND\n        CMAKE_CXX_COMPILER_VERSION VERSION_LESS \"10.0\")\n    # We build POD5 on CentOS 7 in CI, where we have GCC 9 but only the pre-C++11 ABI\n    # See https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html\n    # This forces GCC 9 on other platforms (eg: Ubuntu Focal) to use the same ABI.\n    # The main gain here is being able to use the same conan packages.\n    add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0)\nendif()\n\nif(ENABLE_ADDRESS_SANITIZER)\n    add_compile_options(\"-fsanitize=address\")\n    add_link_options(\"-fsanitize=address\")\nendif()\n\ninclude_directories(\"third_party/include\")\n\nforeach (config \"Release\" \"Debug\")\n    string(TOUPPER \"${config}\" config_upper)\n    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${config_upper} ${CMAKE_BINARY_DIR}/${config}/lib)\n    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${config_upper} ${CMAKE_BINARY_DIR}/${config}/lib)\n    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${config_upper} ${CMAKE_BINARY_DIR}/${config}/bin)\nendforeach()\n\nset(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME \"archive\")\n\ninclude(GenerateExportHeader)\n\nenable_testing()\n\nif (BUILD_PYTHON_WHEEL)\n    find_package(Python ${PYTHON_VERSION} EXACT COMPONENTS Interpreter Development)\n    set(PYBIND11_FINDPYTHON ON)\n    add_subdirectory(third_party/pybind11)\n    install(\n        FILES third_party/pybind11/LICENSE\n        DESTINATION licenses\n        RENAME pybind11.txt\n    )\nendif()\n\nadd_subdirectory(c++)\n\n# The fuzz directory contains both the fuzzers and the regression runners,\n# the latter of which can be built as ordinary tests.\nif (ENABLE_FUZZERS OR NOT POD5_DISABLE_TESTS)\n    add_subdirectory(fuzz)\nendif()\n\n# Install licenses.\ninstall(\n    DIRECTORY ${CMAKE_BINARY_DIR}/pod5_conan_licenses/\n    DESTINATION licenses\n)\ninstall(\n    FILES\n        LICENSE.md\n        third_party/licenses/gsl-lite.txt\n    DESTINATION\n        licenses\n)\n\nif (ENABLE_POD5_PACKAGING)\n    include(pod5_packaging)\nendif()\n"
  },
  {
    "path": "CMakePresets.json",
    "content": "{\n    \"version\": 4,\n    \"include\": [\n        \"cmake/presets/conan-provider.json\",\n        \"cmake/presets/conan-build-options.json\",\n        \"cmake/presets/conan-profiles.json\"\n    ],\n    \"configurePresets\": [\n        {\n            \"name\": \"conan2-linux-gcc9-x86_64-cppstd20-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc9-x86_64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc9-x86_64-cppstd20-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc9-x86_64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc9-x86_64-cppstd17-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc9-x86_64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc9-x86_64-cppstd17-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc9-x86_64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc13-x86_64-cppstd20-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc13-x86_64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc13-x86_64-cppstd20-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc13-x86_64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc13-x86_64-cppstd17-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc13-x86_64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc13-x86_64-cppstd17-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc13-x86_64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-x86_64-cppstd20-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc11-x86_64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-x86_64-cppstd20-release\",\n            \"hidden\": false,\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc11-x86_64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-x86_64-cppstd17-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc11-x86_64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-x86_64-cppstd17-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc11-x86_64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-asan-static-x86_64-cppstd20-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc11-asan-static-x86_64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-asan-static-x86_64-cppstd20-release\",\n            \"hidden\": false,\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc11-asan-static-x86_64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-usan-static-x86_64-cppstd20-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc11-usan-static-x86_64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-usan-static-x86_64-cppstd20-release\",\n            \"hidden\": false,\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc11-usan-static-x86_64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-tsan-static-x86_64-cppstd20-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc11-tsan-static-x86_64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-tsan-static-x86_64-cppstd20-release\",\n            \"hidden\": false,\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc11-tsan-static-x86_64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-asan-static-x86_64-cppstd17-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc11-asan-static-x86_64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-asan-static-x86_64-cppstd17-release\",\n            \"hidden\": false,\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc11-asan-static-x86_64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-usan-static-x86_64-cppstd17-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc11-usan-static-x86_64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-usan-static-x86_64-cppstd17-release\",\n            \"hidden\": false,\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc11-usan-static-x86_64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-tsan-static-x86_64-cppstd17-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc11-tsan-static-x86_64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-tsan-static-x86_64-cppstd17-release\",\n            \"hidden\": false,\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc11-tsan-static-x86_64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc13-aarch64-cppstd20-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc13-aarch64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc13-aarch64-cppstd20-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc13-aarch64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc13-aarch64-cppstd17-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc13-aarch64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc13-aarch64-cppstd17-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc13-aarch64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-aarch64-cppstd20-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc11-aarch64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-aarch64-cppstd20-release\",\n            \"hidden\": false,\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc11-aarch64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-aarch64-cppstd17-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc11-aarch64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc11-aarch64-cppstd17-release\",\n            \"hidden\": false,\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc11-aarch64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc9-aarch64-cppstd17-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc9-aarch64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc9-aarch64-cppstd17-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc9-aarch64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc9-aarch64-cppstd20-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-gcc9-aarch64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-linux-gcc9-aarch64-cppstd20-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-gcc9-aarch64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-macos-appleclang-15.0-aarch64-cppstd17-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-appleclang-15.0-aarch64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-macos-appleclang-15.0-aarch64-cppstd17-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-appleclang-15.0-aarch64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-macos-appleclang-15.0-aarch64-cppstd20-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-appleclang-15.0-aarch64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-macos-appleclang-15.0-aarch64-cppstd20-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-appleclang-15.0-aarch64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-macos-appleclang-16.0-aarch64-cppstd17-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-appleclang-16.0-aarch64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-macos-appleclang-16.0-aarch64-cppstd17-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-appleclang-16.0-aarch64-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-macos-appleclang-16.0-aarch64-cppstd20-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-appleclang-16.0-aarch64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-macos-appleclang-16.0-aarch64-cppstd20-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-appleclang-16.0-aarch64-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-windows-x86_64-vs2019-cppstd17-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-windows-x86_64-vs2019-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-windows-x86_64-vs2019-cppstd17-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-windows-x86_64-vs2019-profile\",\n                \"conan2-cppstd17\"\n            ]\n        },\n        {\n            \"name\": \"conan2-windows-x86_64-vs2019-cppstd20-debug\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-debug\",\n                \"conan2-windows-x86_64-vs2019-profile\",\n                \"conan2-cppstd20\"\n            ]\n        },\n        {\n            \"name\": \"conan2-windows-x86_64-vs2019-cppstd20-release\",\n            \"inherits\": [\n                \"conan2-provider\",\n                \"conan2-release\",\n                \"conan2-windows-x86_64-vs2019-profile\",\n                \"conan2-cppstd20\"\n            ]\n        }\n    ]\n}\n"
  },
  {
    "path": "DEV.md",
    "content": "Development\n===========\n\nIf you want to contribute to pod5_file_format, or our pre-built binaries do not meet your platform requirements, you can build pod5 from source using the instructions in `docs/install.rst`.\n\n### Developing\n\nBuilding the project requires several tools and libraries are available:\n\n- CMake\n- Arrow\n- Zstd\n- Flatbuffers\n- Python\n- setuptools_scm\n\n```bash\n# Docs on installing arrow from here: https://arrow.apache.org/install/\n> sudo apt install -y -V ca-certificates lsb-release wget\n> wget https://apache.jfrog.io/artifactory/arrow/$(lsb_release --id --short | tr 'A-Z' 'a-z')/apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb\n> sudo apt install -y -V ./apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb\n> sudo apt update\n# Now install the rest of the dependencies:\n> sudo apt install cmake libzstd-dev libzstd-dev libflatbuffers-dev libarrow-dev=12.0.1-1\n> pip install setuptools_scm~=7.1\n# Finally start build of POD5:\n> git clone https://github.com/nanoporetech/pod5-file-format.git\n> cd pod5-file-format\n> git submodule update --init --recursive\n> python -m setuptools_scm\n> python ./pod5_make_version.py\n> mkdir build\n> cd build\n> cmake ..\n> make -j\n```\n\n### Pre commit\n\nThe project uses pre-commit to ensure code is consistently formatted, you can set this up using pip:\n\n```bash\n> pip install pre-commit==v2.21.0\n# Install pre-commit hooks in your pod5-file-format repo:\n> cd pod5-file-format\n> pre-commit install\n# Run hooks on all files:\n> pre-commit run --all-files\n```\n\nPython Development\n==================\n\nAfter completing the required build stages above, to create a Python virtual environment for development follow the instructions below .\n\n```bash\n\n> cd python\n> make install\n\n```\n"
  },
  {
    "path": "LICENSE.md",
    "content": "This Source Code Form is subject to the terms of the Mozilla Public\nLicense, v. 2.0. If a copy of the MPL was not distributed with this\nfile, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n©2021 Oxford Nanopore Technologies PLC.\n\n\nMozilla Public License Version 2.0\n==================================\n\n### 1. Definitions\n\n**1.1. “Contributor”**\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n**1.2. “Contributor Version”**\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n**1.3. “Contribution”**\n    means Covered Software of a particular Contributor.\n\n**1.4. “Covered Software”**\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n**1.5. “Incompatible With Secondary Licenses”**\n    means\n\n* **(a)** that the initial Contributor has attached the notice described\n    in Exhibit B to the Covered Software; or\n* **(b)** that the Covered Software was made available under the terms of\n    version 1.1 or earlier of the License, but not also under the\n    terms of a Secondary License.\n\n**1.6. “Executable Form”**\n    means any form of the work other than Source Code Form.\n\n**1.7. “Larger Work”**\n    means a work that combines Covered Software with other material, in\n    a separate file or files, that is not Covered Software.\n\n**1.8. “License”**\n    means this document.\n\n**1.9. “Licensable”**\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n**1.10. “Modifications”**\n    means any of the following:\n\n* **(a)** any file in Source Code Form that results from an addition to,\n    deletion from, or modification of the contents of Covered\n    Software; or\n* **(b)** any new file in Source Code Form that contains any Covered\n    Software.\n\n**1.11. “Patent Claims” of a Contributor**\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n**1.12. “Secondary License”**\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n**1.13. “Source Code Form”**\n    means the form of the work preferred for making modifications.\n\n**1.14. “You” (or “Your”)**\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, “You” includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, “control” means **(a)** the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or **(b)** ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n\n### 2. License Grants and Conditions\n\n#### 2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n* **(a)** under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n* **(b)** under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n#### 2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n#### 2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n* **(a)** for any code that a Contributor has removed from Covered Software;\n    or\n* **(b)** for infringements caused by: **(i)** Your and any other third party's\n    modifications of Covered Software, or **(ii)** the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n* **(c)** under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n#### 2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n#### 2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n#### 2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n#### 2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n\n### 3. Responsibilities\n\n#### 3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n#### 3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n* **(a)** such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n* **(b)** You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n#### 3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n#### 3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n#### 3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n\n### 4. Inability to Comply Due to Statute or Regulation\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: **(a)** comply with\nthe terms of this License to the maximum extent possible; and **(b)**\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n\n### 5. Termination\n\n**5.1.** The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated **(a)** provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and **(b)** on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n**5.2.** If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n**5.3.** In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n\n### 6. Disclaimer of Warranty\n\n> Covered Software is provided under this License on an “as is”\n> basis, without warranty of any kind, either expressed, implied, or\n> statutory, including, without limitation, warranties that the\n> Covered Software is free of defects, merchantable, fit for a\n> particular purpose or non-infringing. The entire risk as to the\n> quality and performance of the Covered Software is with You.\n> Should any Covered Software prove defective in any respect, You\n> (not any Contributor) assume the cost of any necessary servicing,\n> repair, or correction. This disclaimer of warranty constitutes an\n> essential part of this License. No use of any Covered Software is\n> authorized under this License except under this disclaimer.\n\n### 7. Limitation of Liability\n\n> Under no circumstances and under no legal theory, whether tort\n> (including negligence), contract, or otherwise, shall any\n> Contributor, or anyone who distributes Covered Software as\n> permitted above, be liable to You for any direct, indirect,\n> special, incidental, or consequential damages of any character\n> including, without limitation, damages for lost profits, loss of\n> goodwill, work stoppage, computer failure or malfunction, or any\n> and all other commercial damages or losses, even if such party\n> shall have been informed of the possibility of such damages. This\n> limitation of liability shall not apply to liability for death or\n> personal injury resulting from such party's negligence to the\n> extent applicable law prohibits such limitation. Some\n> jurisdictions do not allow the exclusion or limitation of\n> incidental or consequential damages, so this exclusion and\n> limitation may not apply to You.\n\n\n### 8. Litigation\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n\n### 9. Miscellaneous\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n\n### 10. Versions of the License\n\n#### 10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n#### 10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n#### 10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n#### 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\n## Exhibit A - Source Code Form License Notice\n\n    This Source Code Form is subject to the terms of the Mozilla Public\n    License, v. 2.0. If a copy of the MPL was not distributed with this\n    file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\n## Exhibit B - “Incompatible With Secondary Licenses” Notice\n\n    This Source Code Form is \"Incompatible With Secondary Licenses\", as\n    defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "README.md",
    "content": "[![Documentation Status](https://readthedocs.org/projects/pod5-file-format/badge/?version=latest)](https://pod5-file-format.readthedocs.io/)\n\nPOD5 File Format\n================\n\nPOD5 File Format\n================\n\nPOD5 is a file format for storing nanopore dna data in an easily accessible way.\nThe format is able to be written in a streaming manner which allows a sequencing\ninstrument to directly write the format.\n\nData in POD5 is stored using [Apache Arrow](https://github.com/apache/arrow), allowing\nusers to consume data in many languages using standard tools.\n\nWhat does this project contain\n------------------------------\n\nThis project contains a core library for reading and writing POD5 data, and a toolkit for\naccessing this data in other languages.\n\nDocumentation\n-------------\n\nFull documentation is found at https://pod5-file-format.readthedocs.io/\n\n\nUsage\n-----\n\nPOD5 is also bundled as a python module for easy use in scripts, a user can install using:\n\n```bash\n> pip install pod5\n```\n\nThis python module provides the python library to write custom scripts against.\n\nPlease see [examples](./python/pod5/examples) for documentation on using the library.\n\nThe `pod5` package also provides [a selection of tools](./python/pod5/README.md).\n\n\nDesign\n------\n\nFor information about the design of POD5, see the [docs](./docs/README.md).\n\nDevelopment\n-----------\n\nIf you want to contribute to pod5_file_format, or our pre-built binaries do not meet your platform requirements, you can build pod5 from source using the instructions in [DEV.md](DEV.md)\n"
  },
  {
    "path": "benchmarks/.gitignore",
    "content": "*/outputs/\nimage/*.whl\n"
  },
  {
    "path": "benchmarks/README.md",
    "content": "POD5 Benchmarks\n==============\n\nBuilding the benchmark environment\n----------------------------------\n\nTo run benchmarks you first have to build the docker environment to run them:\n\n```bash\n> ./build.sh\n```\n\n\nRunning a benchmark\n-------------------\n\nTo run a benchmark, use the helper script to start the docker image:\n\n```bash\n> ./run_benchmark.sh convert ./path-to-source-files/\n```\n\n\nBenchmarking Results\n--------------------\n\n    Note preliminary results\n\n    Results run on:\n        0.0.16 POD5\n        pyslow5 dev branch (commit 2643310a)\n\n    Benchmark numbers are produced using a GridION.\n\n    Note the benchmarks are run using python APIs, more work is required on C benchmarks.\n\n\n## PCR Dataset\n\nOn dataset a PCR Zymo dataset PAM50264, on 10.4.1 e8.2 data (`pcr_zymo/20220419_1706_2E_PAM50264_3c6f33f1`):\n\n### File sizes\n\n| pod5   | blow5   | fast5   |\n|--------|---------|---------|\n| 37G    | 37G     | 52G     |\n\n### Timings\n\n|                                     | pod5       | blow5      | fast5      |\n|-------------------------------------|------------|------------|------------|\n| convert                             | 197.5 secs | 241.4 secs | Not Run    |\n| find all read ids                   | 10.1 secs  | 1.8 secs   | 5.2 secs   |\n| find all samples                    | 22.3 secs  | 82.5 secs  | 520.6 secs |\n| find selected read ids read number  | 1.1 secs   | 5.8 secs   | 387.1 secs |\n| find selected read ids sample count | 1.5 secs   | 5.7 secs   | 417.8 secs |\n| find selected read ids samples      | 5.3 secs   | 6.4 secs   | 465.6 secs |\n\n```* Note blow5 convert times include the index + merge operation```\n\n\n## InterARTIC Dataset\n\nDataset available at:\nhttps://github.com/Psy-Fer/interARTIC\n\n### File sizes\n\n| pod5   | blow5   | fast5   |\n|--------|---------|---------|\n| 3.3G   | 3.4G    | 6.9G    |\n\n### Timings\n\n|                                     | pod5      | blow5     | fast5     |\n|-------------------------------------|-----------|-----------|-----------|\n| convert                             | 28.6 secs | 21.0 secs | Not Run   |\n| find all read ids                   | 0.5 secs  | 0.5 secs  | 0.7 secs  |\n| find all samples                    | 3.0 secs  | 8.0 secs  | 73.5 secs |\n| find selected read ids read number  | 0.4 secs  | 1.3 secs  | 29.3 secs |\n| find selected read ids sample count | 0.6 secs  | 1.3 secs  | 30.4 secs |\n| find selected read ids samples      | 1.4 secs  | 1.3 secs  | 37.8 secs |\n\n```* Note blow5 convert times include the index + merge operation```\n"
  },
  {
    "path": "benchmarks/build.sh",
    "content": "#!/bin/bash\n\nset -o errexit\nset -o pipefail\nset -o nounset\n# set -o xtrace\n\nscript_dir=$( cd -- \"$( dirname -- \"${BASH_SOURCE[0]}\" )\" &> /dev/null && pwd )\ncd \"${script_dir}\"\n\ncd image/\ndocker build -t pod5-benchmark-base -f Dockerfile.base .\n"
  },
  {
    "path": "benchmarks/convert/run_blow5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\n\n./tools/fast5_to_single_blow5.sh \"$input_dir\" \"$output_dir\"\n"
  },
  {
    "path": "benchmarks/convert/run_pod5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\n\npod5 convert fast5 \"$input_dir\" --output \"$output_dir\"\n"
  },
  {
    "path": "benchmarks/find_all_read_ids/run_blow5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\n\ntools/pyslow5_tests.py \"${input_dir}\"/blow5/*.blow5 \"${output_dir}\" get_all_read_ids\n"
  },
  {
    "path": "benchmarks/find_all_read_ids/run_fast5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\n\ntools/find_and_get_fast5.py \"${input_dir}/fast5\" \"${output_dir}\"\n"
  },
  {
    "path": "benchmarks/find_all_read_ids/run_pod5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\n\n./tools/find_and_get_pod5.py \"${input_dir}/pod5\" \"${output_dir}\"\n"
  },
  {
    "path": "benchmarks/find_all_samples/run_blow5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\n\ntools/pyslow5_tests.py \"${input_dir}\"/blow5/*.blow5 \"${output_dir}\" all_values --get-column samples\n"
  },
  {
    "path": "benchmarks/find_all_samples/run_fast5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\n\ntools/find_and_get_fast5.py \"${input_dir}/fast5\" \"${output_dir}\" --get-column samples\n"
  },
  {
    "path": "benchmarks/find_all_samples/run_pod5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\n\n./tools/find_and_get_pod5.py \"${input_dir}/pod5\" \"${output_dir}\" --get-column samples\n"
  },
  {
    "path": "benchmarks/find_selected_read_ids_read_number/run_blow5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\nfull_output_dir=$3\n\ntools/pyslow5_tests.py \"${input_dir}\"/blow5/*.blow5 \"${output_dir}\" sample_values --get-column read_number --select-ids \"${full_output_dir}/selected_read_ids.csv\"\n"
  },
  {
    "path": "benchmarks/find_selected_read_ids_read_number/run_fast5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\nfull_output_dir=$3\n\ntools/find_and_get_fast5.py \"${input_dir}/fast5\" \"${output_dir}\" --get-column read_number --select-ids \"${full_output_dir}/selected_read_ids.csv\"\n"
  },
  {
    "path": "benchmarks/find_selected_read_ids_read_number/run_pod5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\ntype_output_dir=$2\nfull_output_dir=$3\n\n./tools/find_and_get_pod5.py \"${input_dir}/pod5\" \"${type_output_dir}\" --get-column read_number --select-ids \"${full_output_dir}/selected_read_ids.csv\"\n"
  },
  {
    "path": "benchmarks/find_selected_read_ids_sample_count/run_blow5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\nfull_output_dir=$3\n\ntools/pyslow5_tests.py \"${input_dir}\"/blow5/*.blow5 \"${output_dir}\" sample_values --get-column sample_count --select-ids \"${full_output_dir}/selected_read_ids.csv\"\n"
  },
  {
    "path": "benchmarks/find_selected_read_ids_sample_count/run_fast5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\nfull_output_dir=$3\n\ntools/find_and_get_fast5.py \"${input_dir}/fast5\" \"${output_dir}\" --get-column sample_count --select-ids \"${full_output_dir}/selected_read_ids.csv\"\n"
  },
  {
    "path": "benchmarks/find_selected_read_ids_sample_count/run_pod5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\ntype_output_dir=$2\nfull_output_dir=$3\n\n./tools/find_and_get_pod5.py \"${input_dir}/pod5\" \"${type_output_dir}\" --get-column sample_count --select-ids \"${full_output_dir}/selected_read_ids.csv\"\n"
  },
  {
    "path": "benchmarks/find_selected_read_ids_samples/run_blow5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\nfull_output_dir=$3\n\ntools/pyslow5_tests.py \"${input_dir}\"/blow5/*.blow5 \"${output_dir}\" sample_values --get-column samples --select-ids \"${full_output_dir}/selected_read_ids.csv\"\n"
  },
  {
    "path": "benchmarks/find_selected_read_ids_samples/run_fast5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\nfull_output_dir=$3\n\ntools/find_and_get_fast5.py \"${input_dir}/fast5\" \"${output_dir}\" --get-column samples --select-ids \"${full_output_dir}/selected_read_ids.csv\"\n"
  },
  {
    "path": "benchmarks/find_selected_read_ids_samples/run_pod5.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\ntype_output_dir=$2\nfull_output_dir=$3\n\n./tools/find_and_get_pod5.py \"${input_dir}/pod5\" \"${type_output_dir}\" --get-column samples --select-ids \"${full_output_dir}/selected_read_ids.csv\"\n"
  },
  {
    "path": "benchmarks/image/Dockerfile.base",
    "content": "FROM ubuntu:20.04\n\nRUN apt update && apt install -y wget python3 python3-pip git libzstd-dev\n\nRUN wget https://github.com/nanoporetech/vbz_compression/releases/download/v1.0.1/ont-vbz-hdf-plugin_1.0.1-1.focal_amd64.deb && apt install -y ./ont-vbz-hdf-plugin_1.0.1-1.focal_amd64.deb && rm ont-vbz-hdf-plugin_1.0.1-1.focal_amd64.deb\n\nCOPY ./requirements-benchmarks.txt /\nRUN pip install -r /requirements-benchmarks.txt\n\nCOPY ./install_slow5.sh /\nRUN /install_slow5.sh\nENV PATH=\"/slow5tools-v0.4.0/:$PATH\"\n\nRUN pip install numpy\n\nCOPY ./pod5*.whl /\nRUN pip install *.whl && rm *.whl\n"
  },
  {
    "path": "benchmarks/image/install_slow5.sh",
    "content": "#!/bin/bash\n\nset -e\n\n: \"${SLOW_5_TOOLS_VERSION:=v1.3.0}\"\n: \"${SLOW_5_LIB_VERSION:=v1.3.1}\"\n\napt update\napt install -y libzstd-dev libhdf5-dev\n\nwget \"https://github.com/hasindu2008/slow5tools/releases/download/${SLOW_5_TOOLS_VERSION}/slow5tools-${SLOW_5_TOOLS_VERSION}-release.tar.gz\"\ntar xvf \"slow5tools-${SLOW_5_TOOLS_VERSION}-release.tar.gz\"\nrm \"slow5tools-${SLOW_5_TOOLS_VERSION}-release.tar.gz\"\n(\n    cd \"slow5tools-${SLOW_5_TOOLS_VERSION}\"\n    ./configure\n    make zstd=1 -j \"$(nproc)\"\n)\n\n# pyslow5 must be built with zstd support for fair comparison (otherwise default zlib is slower than zstd)\ngit clone -b \"${SLOW_5_LIB_VERSION}\" https://github.com/hasindu2008/slow5lib\n\n(\n    cd slow5lib/\n\n    echo \"Installing numpy\"\n    pip install numpy\n\n    make pyslow5 -j \"$(nproc)\" 2> build_log.txt || (cat build_log.txt && exit)\n    echo \"Installing pyslow5\"\n    PYSLOW5_ZSTD=1 pip install dist/*.tar.gz\n\n    # adding slow5 C API benchmarks\n    make zstd=1 -j \"$(nproc)\" && test/bench/build.sh\n)\n"
  },
  {
    "path": "benchmarks/image/requirements-benchmarks.txt",
    "content": "h5py\nnumpy\npandas\ntabulate\n"
  },
  {
    "path": "benchmarks/run_benchmarks.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nExample usage:\n```\n> taskset -c 0-10 ./benchmarks/run_benchmarks.py ./input_files/ \\\n    ./benchmark-outputs/ --skip-to-benchmark find_all_samples\n```\n\"\"\"\n\nimport argparse\nimport json\nimport shutil\nimport subprocess\nimport time\nfrom collections import namedtuple\nfrom pathlib import Path\n\nimport tabulate\n\nBenchmark = namedtuple(\n    \"Benchmark\",\n    [\"name\", \"file_types\", \"checks\", \"input_benchmark\", \"pre_run\", \"post_run_fixup\"],\n)\n\nBENCHMARK_ROOT = Path(__file__).resolve().parent\n\nPOD5_FILE_TYPE = \"pod5\"\nBLOW5_FILE_TYPE = \"blow5\"\nFAST5_FILE_TYPE = \"fast5\"\nALL_FILE_TYPES = [POD5_FILE_TYPE, BLOW5_FILE_TYPE, FAST5_FILE_TYPE]\n\n\ndef du(path):\n    \"\"\"disk usage in human readable format (e.g. '2,1GB')\"\"\"\n    return subprocess.check_output([\"du\", \"-sh\", path]).split()[0].decode(\"utf-8\")\n\n\ndef generate_report(input_dir, output_dir, timing_results):\n    report = \"\"\n\n    skipped_benchmarks = len(ALL_BENCHMARKS) - len(timing_results)\n\n    skipped = \"\"\n    if skipped_benchmarks != 0:\n        skipped = f\", skipped {skipped_benchmarks}\"\n    report += f\"Ran {len(timing_results)} benchmarks{skipped}\\n\\n\"\n    report += f\"Input data was {input_dir}\\n\\n\"\n\n    report += \"File sizes\\n\"\n    report += \"----------\\n\\n\"\n\n    convert_output_dir = output_dir / \"convert\"\n    sizes = []\n    for file_type in ALL_FILE_TYPES:\n        file_type_dir = convert_output_dir / file_type\n        if file_type_dir.exists:\n            sizes.append(du(file_type_dir))\n        else:\n            sizes.append(\"Not Run\")\n    report += (\n        tabulate.tabulate([sizes], headers=ALL_FILE_TYPES, tablefmt=\"github\") + \"\\n\\n\"\n    )\n\n    report += \"Timings\\n\"\n    report += \"-------\\n\\n\"\n    results = []\n    for benchmark in ALL_BENCHMARKS:\n        row = [benchmark.name.replace(\"_\", \" \")]\n        results.append(row)\n\n        if benchmark.name in timing_results:\n            timings = timing_results[benchmark.name]\n            for file_type in ALL_FILE_TYPES:\n                if file_type in timings:\n                    row.append(f\"{timings[file_type]:.1f} secs\")\n                else:\n                    row.append(\"Not Run\")\n        else:\n            for file_type in ALL_FILE_TYPES:\n                row.append(\"Not Run\")\n\n    results_headers = [\"\"] + ALL_FILE_TYPES\n    report += (\n        tabulate.tabulate(results, headers=results_headers, tablefmt=\"github\") + \"\\n\"\n    )\n    return report\n\n\ndef check_read_ids(benchmark, file_types, output_dir, only_format):\n    if only_format is not None:\n        print(\"Not checking read ids - only one format executed\")\n        return\n\n    csv_check_files = []\n    for file_type in file_types:\n        csv_check_files.append(output_dir / file_type / \"read_ids.csv\")\n\n    for a, b in zip(csv_check_files[1:], csv_check_files):\n        subprocess.run(\n            [BENCHMARK_ROOT / \"tools\" / \"check_csvs_consistent.py\", a, b], check=True\n        )\n\n\ndef check_file_sizes(benchmark, file_types, output_dir, only_format):\n    print(\"File sizes for output dir\")\n    subprocess.run([\"du\", \"-sh\"] + list(output_dir.glob(\"*\")), check=True)\n\n\ndef copy_fast5_files(benchmark, input_dir, output_dir):\n    shutil.copytree(input_dir, output_dir / \"fast5\")\n\n\ndef randomly_select_read_ids(benchmark, input_dir, output_dir):\n    print(\"Randomly selecting read ids for benchmark\")\n\n    subprocess.run(\n        [\n            BENCHMARK_ROOT / \"tools\" / \"find_and_get_pod5.py\",\n            input_dir / \"pod5\",\n            output_dir,\n        ],\n        check=True,\n    )\n    subprocess.run(\n        [\n            BENCHMARK_ROOT / \"tools\" / \"select-random-ids.py\",\n            output_dir / \"read_ids.csv\",\n            output_dir / \"selected_read_ids.csv\",\n            \"--select-ratio\",\n            \"0.1\",\n        ],\n        check=True,\n    )\n\n\nALL_BENCHMARKS = [\n    Benchmark(\n        \"convert\",\n        [POD5_FILE_TYPE, BLOW5_FILE_TYPE],\n        [check_file_sizes],\n        input_benchmark=None,\n        post_run_fixup=copy_fast5_files,\n        pre_run=None,\n    ),\n    Benchmark(\n        \"find_all_read_ids\",\n        ALL_FILE_TYPES,\n        [check_read_ids],\n        input_benchmark=\"convert\",\n        post_run_fixup=None,\n        pre_run=None,\n    ),\n    Benchmark(\n        \"find_all_samples\",\n        ALL_FILE_TYPES,\n        [check_read_ids],\n        input_benchmark=\"convert\",\n        post_run_fixup=None,\n        pre_run=None,\n    ),\n    Benchmark(\n        \"find_selected_read_ids_read_number\",\n        ALL_FILE_TYPES,\n        [check_read_ids],\n        input_benchmark=\"convert\",\n        post_run_fixup=None,\n        pre_run=randomly_select_read_ids,\n    ),\n    Benchmark(\n        \"find_selected_read_ids_sample_count\",\n        ALL_FILE_TYPES,\n        [check_read_ids],\n        input_benchmark=\"convert\",\n        post_run_fixup=None,\n        pre_run=randomly_select_read_ids,\n    ),\n    Benchmark(\n        \"find_selected_read_ids_samples\",\n        ALL_FILE_TYPES,\n        [check_read_ids],\n        input_benchmark=\"convert\",\n        post_run_fixup=None,\n        pre_run=randomly_select_read_ids,\n    ),\n]\n\n\ndef run_benchmark(benchmark, input_dir, output_dir, only_format=None):\n    if output_dir.exists():\n        print(\"Removing old output dir\")\n        shutil.rmtree(output_dir)\n\n    file_types = benchmark.file_types if benchmark.file_types else ALL_FILE_TYPES\n\n    time_results = {}\n\n    if benchmark.pre_run:\n        benchmark.pre_run(benchmark, input_dir, output_dir)\n\n    for file_type in file_types:\n        if only_format is not None and only_format != file_type:\n            print(f\"## Skipping for file type {file_type}:\")\n            continue\n        print(f\"## Running for file type {file_type}:\")\n        file_type_output_dir = output_dir / file_type\n        file_type_output_dir.mkdir(exist_ok=True, parents=True)\n        start = time.time()\n        subprocess.run(\n            [\n                BENCHMARK_ROOT / benchmark.name / f\"run_{file_type}.sh\",\n                input_dir,\n                file_type_output_dir,\n                output_dir,\n            ],\n            check=True,\n            cwd=BENCHMARK_ROOT,\n        )\n        end = time.time()\n        duration_secs = end - start\n        time_results[file_type] = duration_secs\n        print(f\"## Took {duration_secs:.2f} seconds\")\n\n    if benchmark.post_run_fixup:\n        benchmark.post_run_fixup(benchmark, input_dir, output_dir)\n\n    if benchmark.checks:\n        print(\"## Running checks\")\n        for check in benchmark.checks:\n            check(benchmark, file_types, output_dir, only_format)\n\n    return time_results\n\n\ndef run_benchmarks(args):\n    timing_results = {}\n\n    input_dir = args.input_dir.resolve()\n    output_dir = args.output_dir.resolve()\n\n    skip_list = []\n    if args.skip_to_benchmark:\n        found = False\n        for benchmark in ALL_BENCHMARKS:\n            if benchmark.name == args.skip_to_benchmark:\n                found = True\n\n            if not found:\n                skip_list.append(benchmark.name)\n\n    for benchmark in ALL_BENCHMARKS:\n        if benchmark.name in skip_list:\n            print(f\"# Skipping benchmark {benchmark.name}\")\n            continue\n        print(f\"# Running benchmark {benchmark.name}:\")\n        benchmark_input_dir = input_dir\n        if benchmark.input_benchmark:\n            benchmark_input_dir = output_dir / benchmark.input_benchmark\n        timing_results[benchmark.name] = run_benchmark(\n            benchmark,\n            benchmark_input_dir,\n            output_dir / benchmark.name,\n            args.only_format,\n        )\n\n    report = generate_report(input_dir, output_dir, timing_results)\n    print(report)\n\n    with open(args.output_dir / \"timings.json\", \"w\") as f:\n        f.write(json.dumps(timing_results, indent=2))\n    with open(args.output_dir / \"report.md\", \"w\") as f:\n        f.write(report)\n\n\ndef main():\n    parser = argparse.ArgumentParser(\"Run Benchmarks for POD5 format\")\n    parser.add_argument(\"input_dir\", type=Path)\n    parser.add_argument(\"output_dir\", type=Path)\n    parser.add_argument(\n        \"--skip-to-benchmark\",\n        type=str,\n        help=\"Start benchmarking from a named benchmark\",\n    )\n    parser.add_argument(\n        \"--only-format\",\n        type=str,\n        help=\"Only run benchmarks for a single format\",\n    )\n\n    args = parser.parse_args()\n\n    run_benchmarks(args)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "benchmarks/run_benchmarks_in_docker.sh",
    "content": "#!/bin/bash\n\nset -e\n\ninput_dir=$(readlink -f \"$1\")\noutput_dir=\"$(pwd)/pod5-benchmark-outputs\"\nmkdir -p \"${output_dir}\"\n\nscript_dir=$( cd -- \"$( dirname -- \"${BASH_SOURCE[0]}\" )\" &> /dev/null && pwd )\n\necho \"Running benchmark on input '${input_dir}'\"\n\ndocker run --rm -it -v\"${input_dir}\":/input -v\"${output_dir}\":/outputs -v\"${script_dir}\"/:/benchmarks pod5-benchmark-base /benchmarks/tools/run_benchmarks_docker_entry.sh\n"
  },
  {
    "path": "benchmarks/tools/check_csvs_consistent.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport sys\n\nimport pandas as pd\nfrom pandas.testing import assert_frame_equal\n\n\ndef check_consistency(df1, df2):\n    df1 = df1.sort_values(\"read_id\", ignore_index=True)\n    df2 = df2.sort_values(\"read_id\", ignore_index=True)\n\n    assert_frame_equal(df1, df2)\n\n    print(\"Data frames are consistent\")\n    sys.exit(0)\n\n\ndef main():\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"input_a\")\n    parser.add_argument(\"input_b\")\n\n    args = parser.parse_args()\n\n    a = pd.read_csv(args.input_a)\n    b = pd.read_csv(args.input_b)\n\n    print(f\"Check consistency of files {args.input_a} and {args.input_b}\")\n    check_consistency(a, b)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "benchmarks/tools/fast5_to_single_blow5.sh",
    "content": "#!/bin/bash\n\ninput_path=$1\noutput_path=$2\n\nmkdir -p \"$output_path\"\n\ntemp_dir=\"${output_path}/tmp\"\nmkdir -p \"$temp_dir\"\n\n# specific options (-c zstd -s svb-zd) must be provided to slow5tools to create compression comparable to vbz\n# also number of processes/threads must be set to 10 to match with default value in pod5_convert\n# however, the svb-zd stream variable byte + zig-zag delta implementation in slow5 mirrors\n# ONT's previous 32 bit zigzag delta, where as pod5 is using a newer 16 bit zigzag delta with SIMD optimisations\n# so pod5 has the added performance benefit of using the newer zigzag delta\n# slow5 compression methods are modular, so we can easily add the new one iff necessary\nslow5tools f2s \"$input_path\" -d \"$temp_dir\" -p 10 -c zstd -s svb-zd\n\n# Most comparable to have one file for both formats:\nslow5tools cat \"$temp_dir -o $output_path/file.blow5\" || slow5tools merge \"$temp_dir\" -o \"$output_path/file.blow5\" -t 10 -c zstd -s svb-zd\n#if the files are from the same run ID, slow5tools cat can be used, which is significantly faster\n#slow5tools cat $temp_dir -o $output_path/file.blow5\n\nrm -r \"$temp_dir\"\n\n# Index will get generated on first test anyway, we should do it now to give best results later:\n# current slow5tools implementation decompresses the whole record for indexing and is not efficient\n# the specification supports partial decompress of the record (also signal chunking if necessary)\nslow5tools index \"$output_path/file.blow5\"\n"
  },
  {
    "path": "benchmarks/tools/find_and_get_fast5.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nfrom pathlib import Path\n\nimport h5py\nimport numpy\nimport pandas as pd\n\n\ndef select_reads(file, selection):\n    if selection is not None:\n        for read in selection:\n            path = f\"/read_{read}\"\n            if path not in file:\n                continue\n            yield read, path\n    else:\n        for key in file.keys():\n            if key.startswith(\"read_\"):\n                yield key[5:], key\n\n\ndef run(input_dir, output, select_read_ids=None, get_columns=[]):\n    output.mkdir(parents=True, exist_ok=True)\n\n    if select_read_ids is not None:\n        print(f\"Selecting {len(select_read_ids)} specific read ids\")\n    if get_columns is not None:\n        print(f\"Selecting columns: {get_columns}\")\n\n    read_ids = []\n    extracted_columns = {\"read_id\": read_ids}\n    print(f\"Search for input files in {input_dir}\")\n    for file in input_dir.glob(\"*.fast5\"):\n        print(f\"Searching for reads in {file}\")\n\n        file = h5py.File(file, \"r\")\n\n        for read_id, read_path in select_reads(file, select_read_ids):\n            read_ids.append(read_id)\n\n            for c in get_columns:\n                if c not in extracted_columns:\n                    extracted_columns[c] = []\n                col = extracted_columns[c]\n\n                if c == \"read_number\":\n                    col.append(file[f\"{read_path}/Raw\"].attrs[\"read_number\"])\n                elif c == \"sample_count\":\n                    col.append(len(file[f\"{read_path}/Raw\"][\"Signal\"]))\n                elif c == \"samples\":\n                    col.append(numpy.sum(file[f\"{read_path}/Raw\"][\"Signal\"]))\n\n    df = pd.DataFrame(extracted_columns)\n    print(f\"Selected {len(read_ids)} items\")\n    df.to_csv(output / \"read_ids.csv\", index=False)\n\n\ndef main():\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"input\", type=Path)\n    parser.add_argument(\"output\", type=Path)\n    parser.add_argument(\n        \"--select-ids\",\n        type=str,\n        help=\"CSV file with a read_id column, listing ids to find in input files\",\n    )\n    parser.add_argument(\n        \"--get-column\",\n        default=[],\n        nargs=\"+\",\n        type=str,\n        help=\"Add columns that should be extracted\",\n    )\n\n    args = parser.parse_args()\n\n    select_read_ids = None\n    if args.select_ids:\n        select_read_ids = pd.read_csv(args.select_ids)[\"read_id\"]\n\n    run(\n        args.input,\n        args.output,\n        select_read_ids=select_read_ids,\n        get_columns=args.get_column,\n    )\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "benchmarks/tools/find_and_get_pod5.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport multiprocessing as mp\nimport tempfile\nfrom collections import namedtuple\nfrom pathlib import Path\nfrom queue import Empty\n\nimport numpy\nimport pandas as pd\n\nimport pod5 as p5\n\nSelectReadIdsData = namedtuple(\n    \"SelectReadIdsData\", [\"path\", \"slice_start\", \"slice_end\", \"shape\"]\n)\n\n\ndef load_mapped_ids(select_read_ids_data):\n    \"\"\"Load a set of read ids from a mmapped file on disk\"\"\"\n    select_read_ids_all = numpy.memmap(\n        select_read_ids_data.path,\n        dtype=numpy.uint8,\n        mode=\"r+\",\n        shape=select_read_ids_data.shape,\n    )\n    return select_read_ids_all[\n        select_read_ids_data.slice_start : select_read_ids_data.slice_end\n    ]\n\n\ndef do_batch_work(filename, batches, column, mode, result_q):\n    \"\"\"\n    Per process worker to do loading of data from a set of batches\n    \"\"\"\n\n    read_ids = []\n    vals = []\n    extracted_columns = {\"read_id\": read_ids, column: vals}\n\n    if column == \"samples\":\n        file = p5.Reader(filename)\n        for batch in file.read_batches(batch_selection=batches, preload={\"samples\"}):\n            read_ids.extend(p5.format_read_ids(batch.read_id_column))\n\n            for read in batch.reads():\n                vals.append(numpy.sum(read.signal))\n    else:\n        print(f\"Unknown column {column}\")\n    result_q.put(pd.DataFrame(extracted_columns))\n\n\ndef do_search_work(files, select_read_ids_data, column, mode, result_q):\n    \"\"\"\n    Per process worker to do loading of data from a number of read ids\n    \"\"\"\n    select_read_ids = load_mapped_ids(select_read_ids_data)\n\n    read_ids = []\n    vals = []\n    extracted_columns = {\"read_id\": read_ids, column: vals}\n\n    if column == \"samples\":\n        for filename in files:\n            file = p5.Reader(filename)\n            for batch in file.read_batches(select_read_ids, preload={\"samples\"}):\n                read_ids.extend(p5.format_read_ids(batch.read_id_column))\n                vals.extend([numpy.sum(s) for s in batch.cached_samples_column])\n    else:\n        print(f\"Unknown column {column}\")\n    result_q.put(pd.DataFrame(extracted_columns))\n\n\ndef run_multiprocess(files, output, select_read_ids=None, column=None, mode=None):\n    \"\"\"\n    Do work across a number of python multiprocesses\n    \"\"\"\n    mp.set_start_method(\"spawn\")\n\n    if select_read_ids is not None:\n        print(\"Placing select read id data on disk for mmapping:\")\n        numpy_select_read_ids = p5.pack_read_ids(select_read_ids)\n\n        # Copy data to memory-map\n        fp = tempfile.NamedTemporaryFile()\n        fp.close()\n        mapped_select_read_ids = numpy.memmap(\n            fp.name, dtype=numpy.uint8, mode=\"w+\", shape=numpy_select_read_ids.shape\n        )\n        numpy.copyto(mapped_select_read_ids, numpy_select_read_ids)\n        select_read_ids_mmap_path = Path(fp.name)\n\n    result_queue = mp.Queue()\n    runners = 10\n\n    processes = []\n    if select_read_ids is not None:\n        approx_chunk_size = max(1, len(select_read_ids) // runners)\n        start_index = 0\n        while start_index < len(select_read_ids):\n            select_read_ids_data = SelectReadIdsData(\n                select_read_ids_mmap_path,\n                start_index,\n                start_index + approx_chunk_size,\n                numpy_select_read_ids.shape,\n            )\n\n            p = mp.Process(\n                target=do_search_work,\n                args=(files, select_read_ids_data, column, mode, result_queue),\n            )\n            p.start()\n            processes.append(p)\n            start_index += approx_chunk_size\n    else:\n        for filename in files:\n            file = p5.Reader(filename)\n            batches = list(range(file.batch_count))\n            approx_chunk_size = max(1, len(batches) // runners)\n            start_index = 0\n            while start_index < len(batches):\n                select_batches = batches[start_index : start_index + approx_chunk_size]\n                p = mp.Process(\n                    target=do_batch_work,\n                    args=(filename, select_batches, column, mode, result_queue),\n                )\n                p.start()\n                processes.append(p)\n                start_index += len(select_batches)\n\n    print(\"Wait for processes...\")\n\n    items = []\n    while len(items) < len(processes):\n        try:\n            item = result_queue.get(timeout=0.5)\n            items.append(item)\n        except Empty:\n            continue\n\n    for p in processes:\n        p.join()\n\n    return pd.concat(items)\n\n    if select_read_ids is not None:\n        select_read_ids_mmap_path.unlink()\n\n\ndef run_get_read_ids(files):\n    \"\"\"\n    Load all read ids from the file.\n    \"\"\"\n    read_ids = []\n    for filename in files:\n        file = p5.Reader(filename)\n        for batch in file.read_batches():\n            read_ids.extend(p5.format_read_ids(batch.read_id_column))\n    return pd.DataFrame({\"read_id\": read_ids})\n\n\ndef run_select(files, select_read_ids, column):\n    \"\"\"\n    Load column from a specific set of read ids\n    \"\"\"\n    read_ids = []\n    vals = []\n    extracted_columns = {\"read_id\": read_ids, column: vals}\n\n    for filename in files:\n        file = p5.Reader(filename)\n        if column == \"sample_count\":\n            for batch in file.read_batches(select_read_ids, preload={\"sample_count\"}):\n                read_id_selection = batch.read_id_column\n                read_ids.extend(p5.format_read_ids(read_id_selection))\n                vals.extend(batch.cached_sample_count_column)\n        else:\n            col_name = f\"{column}_column\"\n            for batch in file.read_batches(select_read_ids):\n                read_id_selection = batch.read_id_column\n                read_ids.extend(p5.format_read_ids(read_id_selection))\n\n                read_number_selection = getattr(batch, col_name)\n                vals.extend(read_number_selection)\n\n    return pd.DataFrame(extracted_columns)\n\n\ndef run_batched(files, column):\n    \"\"\"\n    Load column from a all reads\n    \"\"\"\n    read_ids = []\n    vals = []\n    extracted_columns = {\"read_id\": read_ids, column: vals}\n\n    for filename in files:\n        file = p5.Reader(filename)\n        if column == \"sample_count\":\n            for batch in file.read_batches(preload={\"sample_count\"}):\n                read_ids.extend(p5.format_read_ids(batch.read_id_column))\n                vals.extend(batch.cached_sample_count_column)\n        else:\n            col_name = f\"{column}_column\"\n            for batch in file.read_batches():\n                read_ids.extend(p5.format_read_ids(batch.read_id_column))\n                vals.extend(getattr(batch, col_name).to_numpy())\n\n    return pd.DataFrame(extracted_columns)\n\n\ndef main():\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"input\", type=Path)\n    parser.add_argument(\"output\", type=Path)\n    parser.add_argument(\n        \"--select-ids\",\n        type=str,\n        help=\"CSV file with a read_id column, listing ids to find in input files\",\n    )\n    parser.add_argument(\n        \"--get-column\",\n        default=None,\n        type=str,\n        help=\"Add column that should be extracted\",\n    )\n    args = parser.parse_args()\n\n    select_read_ids = None\n    if args.select_ids:\n        select_read_ids = pd.read_csv(args.select_ids)[\"read_id\"]\n\n    if select_read_ids is not None:\n        print(f\"Selecting {len(select_read_ids)} specific read ids\")\n    if args.get_column is not None:\n        print(f\"Selecting column: {args.get_column}\")\n\n    mode = None\n\n    print(f\"Search for input files in {args.input}\")\n    files = list(args.input.glob(\"*.pod5\"))\n    print(f\"Searching in {[str(f) for f in files]}\")\n\n    # Run benchmark using most appropriate method:\n    if args.get_column is None:\n        df = run_get_read_ids(files)\n    elif args.get_column == \"samples\":\n        # Because we the \"samples\" column to be the sum\n        # of all samples in input data, it is quicker to use\n        # python multiprocessing to split the summing work:\n        df = run_multiprocess(\n            files,\n            args.output,\n            select_read_ids=select_read_ids,\n            column=args.get_column,\n            mode=mode,\n        )\n    elif args.select_ids:\n        df = run_select(\n            files,\n            select_read_ids=select_read_ids,\n            column=args.get_column,\n        )\n    else:\n        df = run_batched(\n            files,\n            column=args.get_column,\n        )\n\n    print(f\"Selected {len(df)} items\")\n    args.output.mkdir(parents=True, exist_ok=True)\n    df.to_csv(args.output / \"read_ids.csv\", index=False)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "benchmarks/tools/pyslow5_tests.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport multiprocessing as mp\nfrom pathlib import Path\nfrom queue import Empty\n\nimport numpy\nimport pandas as pd\nimport pyslow5\n\n\ndef random_access(s5_file, read_list, col, result_q):\n    file = pyslow5.Open(str(s5_file), \"r\")\n    print(\"processing \", s5_file)\n    read_ids = []\n    extracted_columns = {\"read_id\": read_ids}\n    extracted_columns[col] = []\n    vals = extracted_columns[col]\n    if col == \"samples\":\n        for read in file.get_read_list_multi(read_list, threads=10, batchsize=5000):\n            read_ids.append(read[\"read_id\"])\n            vals.append(numpy.sum(read[\"signal\"]))\n    elif col == \"sample_count\":\n        for read in file.get_read_list_multi(read_list, threads=10, batchsize=5000):\n            read_ids.append(read[\"read_id\"])\n            vals.append(read[\"len_raw_signal\"])\n    else:\n        for read in file.get_read_list_multi(\n            read_list, threads=10, batchsize=5000, pA=False, aux=col\n        ):\n            read_ids.append(read[\"read_id\"])\n            vals.append(read[col])\n    result_q.put(pd.DataFrame(extracted_columns))\n\n\ndef run(s5_file, benchmark, select_read_ids, col):\n    if benchmark == \"get_all_read_ids\":\n        read_ids = []\n        extracted_columns = {\"read_id\": read_ids}\n        file = pyslow5.Open(str(s5_file), \"r\")\n        print(\"processing \", s5_file)\n        read_ids, num_reads = file.get_read_ids()\n        extracted_columns = {\"read_id\": read_ids}\n\n    elif benchmark == \"sample_values\":\n        mp.set_start_method(\"spawn\")\n        result_queue = mp.Queue()\n        runners = 10\n        processes = []\n        approx_chunk_size = max(1, len(select_read_ids) // runners)\n        select_ids = []\n        for i in range(0, len(select_read_ids), approx_chunk_size):\n            for j in range(i, min(len(select_read_ids), i + approx_chunk_size)):\n                select_ids.append(select_read_ids[j])\n            p = mp.Process(\n                target=random_access, args=(s5_file, select_ids, col, result_queue)\n            )\n            p.start()\n            processes.append(p)\n            select_ids = []\n\n        print(\"Wait for processes...\")\n        items = []\n        while len(items) < len(processes):\n            try:\n                item = result_queue.get(timeout=0.5)\n                items.append(item)\n            except Empty:\n                continue\n\n        for p in processes:\n            p.join()\n\n        df = pd.concat(items)\n        return df\n\n    elif benchmark == \"all_values\":\n        read_ids = []\n        extracted_columns = {\"read_id\": read_ids}\n        file = pyslow5.Open(str(s5_file), \"r\")\n        print(\"processing \", s5_file)\n        extracted_columns[col] = []\n        vals = extracted_columns[col]\n        if col == \"samples\":\n            for read in file.seq_reads_multi(threads=10, batchsize=5000):\n                read_ids.append(read[\"read_id\"])\n                vals.append(numpy.sum(read[\"signal\"]))\n        elif col == \"sample_count\":\n            for read in file.seq_reads_multi(threads=10, batchsize=5000):\n                read_ids.append(read[\"read_id\"])\n                vals.append(read[\"len_raw_signal\"])\n        else:\n            for read in file.seq_reads_multi(\n                threads=10, batchsize=5000, pA=False, aux=col\n            ):\n                read_ids.append(read[\"read_id\"])\n                vals.append(read[col])\n\n    return pd.DataFrame(extracted_columns)\n\n\ndef main():\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"input\", type=Path)\n    parser.add_argument(\"output\", type=Path)\n    parser.add_argument(\n        \"benchmark\",\n        type=str,\n        choices=[\"get_all_read_ids\", \"sample_values\", \"all_values\"],\n        help=\"which benchmark to run\",\n    )\n    parser.add_argument(\n        \"--select-ids\",\n        type=str,\n        help=\"CSV file with a read_id column, listing ids to find in input files\",\n    )\n    parser.add_argument(\n        \"--get-column\",\n        default=None,\n        type=str,\n        help=\"Add columns that should be extracted\",\n    )\n\n    args = parser.parse_args()\n\n    args.output.mkdir(parents=True, exist_ok=True)\n    select_read_ids = None\n    select_reads = []\n    if args.select_ids:\n        select_read_ids = pd.read_csv(args.select_ids)[\"read_id\"]\n        for i in select_read_ids:\n            select_reads.append(i)\n\n    print(f\"Num of select_reads: {len(select_reads)}\")\n\n    df = run(\n        args.input,\n        args.benchmark,\n        select_read_ids=select_reads,\n        col=args.get_column,\n    )\n    print(f\"Selected {len(df)} items\")\n    df.to_csv(args.output / \"read_ids.csv\", index=False)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "benchmarks/tools/run_benchmarks_docker_entry.sh",
    "content": "#!/bin/bash\n\n# Use taskset to limit benchmarks to specific cores, ensuring a fair test of limited resources:\ntaskset -c 0-10 /benchmarks/run_benchmarks.py /input /outputs\n"
  },
  {
    "path": "benchmarks/tools/select-random-ids.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nfrom pathlib import Path\n\nimport pandas as pd\n\n\ndef main():\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"input_csv\", type=Path)\n    parser.add_argument(\"output_csv\", type=Path)\n    parser.add_argument(\"--select-ratio\", type=float)\n\n    args = parser.parse_args()\n\n    df = pd.read_csv(args.input_csv)\n\n    selected_rows_df = df.sample(frac=args.select_ratio)\n\n    args.output_csv.parent.mkdir(parents=True, exist_ok=True)\n    selected_rows_df.to_csv(args.output_csv)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "c++/CMakeLists.txt",
    "content": "\n\nif (ENABLE_CONAN)\n    find_package(Arrow REQUIRED CONFIG)\n    find_package(Flatbuffers REQUIRED CONFIG)\n    find_package(zstd REQUIRED CONFIG)\n    find_package(ZLIB REQUIRED CONFIG)\n\n    if (${CMAKE_SYSTEM_NAME} STREQUAL \"Linux\")\n        find_package(jemalloc REQUIRED CONFIG)\n    endif()\nelse()\n    find_package(Arrow REQUIRED)\n    find_package(Flatbuffers REQUIRED)\n    find_package(zstd REQUIRED)\n    find_package(ZLIB REQUIRED)\n\n    # Our non-conan ubuntu CI build has a different name for this target\n    if (NOT CONAN2)\n        add_library(arrow::arrow INTERFACE IMPORTED)\n        target_link_libraries(arrow::arrow INTERFACE Arrow::arrow_shared)\n    endif()\nendif()\n\nfind_package(Threads REQUIRED)\n\nfind_program(\n    FLATBUFFERS_FLATC_EXECUTABLE\n    flatc\n)\ninclude(BuildFlatBuffers)\n\nconfigure_file(\n    pod5_format/version.h.in\n    pod5_format/version.h\n)\n\nset(pod5_library_type STATIC)\nif (BUILD_SHARED_LIB)\n    set(pod5_library_type SHARED)\nendif()\n\nadd_library(pod5_format ${pod5_library_type}\n    pod5_format/file_recovery.h\n    pod5_format/file_writer.cpp\n    pod5_format/file_writer.h\n    pod5_format/file_reader.cpp\n    pod5_format/file_reader.h\n    pod5_format/file_updater.cpp\n    pod5_format/file_updater.h\n\n    pod5_format/async_signal_loader.cpp\n    pod5_format/async_signal_loader.h\n\n    pod5_format/schema_metadata.cpp\n    pod5_format/table_reader.h\n    pod5_format/schema_field_builder.h\n\n    pod5_format/read_table_reader.cpp\n    pod5_format/read_table_reader.h\n    pod5_format/read_table_schema.cpp\n    pod5_format/read_table_schema.h\n    pod5_format/read_table_writer.cpp\n    pod5_format/read_table_writer.h\n    pod5_format/read_table_writer_utils.cpp\n    pod5_format/read_table_writer_utils.h\n    pod5_format/read_table_utils.cpp\n    pod5_format/read_table_utils.h\n\n    pod5_format/run_info_table_reader.cpp\n    pod5_format/run_info_table_reader.h\n    pod5_format/run_info_table_schema.cpp\n    pod5_format/run_info_table_schema.h\n    pod5_format/run_info_table_writer.cpp\n    pod5_format/run_info_table_writer.h\n\n    pod5_format/signal_compression.cpp\n    pod5_format/signal_compression.h\n    pod5_format/signal_table_reader.cpp\n    pod5_format/signal_table_reader.h\n    pod5_format/signal_table_schema.cpp\n    pod5_format/signal_table_schema.h\n    pod5_format/signal_table_writer.cpp\n    pod5_format/signal_table_writer.h\n    pod5_format/signal_table_utils.h\n    pod5_format/signal_builder.h\n\n    pod5_format/c_api.cpp\n    pod5_format/c_api.h\n\n    pod5_format/expandable_buffer.h\n    pod5_format/io_manager.cpp\n    pod5_format/io_manager.h\n    pod5_format/memory_pool.cpp\n    pod5_format/memory_pool.h\n    pod5_format/result.h\n    pod5_format/schema_utils.cpp\n    pod5_format/schema_utils.h\n    pod5_format/table_reader.cpp\n    pod5_format/table_reader.h\n    pod5_format/thread_pool.cpp\n    pod5_format/thread_pool.h\n    pod5_format/tuple_utils.h\n    pod5_format/types.cpp\n    pod5_format/types.h\n    pod5_format/uuid.h\n\n    pod5_format/migration/migration.cpp\n    pod5_format/migration/migration.h\n    pod5_format/migration/migration_utils.h\n    pod5_format/migration/v0_to_v1.cpp\n    pod5_format/migration/v1_to_v2.cpp\n    pod5_format/migration/v2_to_v3.cpp\n    pod5_format/migration/v3_to_v4.cpp\n\n    pod5_format/internal/async_output_stream.h\n    pod5_format/internal/combined_file_utils.h\n    pod5_format/internal/linux_output_stream.h\n\n    pod5_format/svb16/common.hpp\n    pod5_format/svb16/decode.hpp\n    pod5_format/svb16/decode_scalar.hpp\n    pod5_format/svb16/decode_x64.hpp\n    pod5_format/svb16/encode.hpp\n    pod5_format/svb16/encode_scalar.hpp\n    pod5_format/svb16/encode_x64.hpp\n    pod5_format/svb16/intrinsics.hpp\n    pod5_format/svb16/shuffle_tables.hpp\n    pod5_format/svb16/simd_detect_x64.hpp\n)\n\nset(public_headers)\nlist(APPEND public_headers\n    pod5_format/file_writer.h\n    pod5_format/file_reader.h\n\n    pod5_format/schema_metadata.h\n\n    pod5_format/read_table_reader.h\n    pod5_format/read_table_schema.h\n    pod5_format/read_table_writer.h\n    pod5_format/read_table_writer_utils.h\n    pod5_format/read_table_utils.h\n\n    pod5_format/run_info_table_writer.h\n    pod5_format/run_info_table_reader.h\n    pod5_format/run_info_table_schema.h\n\n    pod5_format/signal_compression.h\n    pod5_format/signal_table_reader.h\n    pod5_format/signal_table_schema.h\n    pod5_format/signal_table_writer.h\n    pod5_format/signal_table_utils.h\n    pod5_format/signal_builder.h\n    pod5_format/uuid.h\n\n    pod5_format/c_api.h\n\n    pod5_format/expandable_buffer.h\n    pod5_format/file_output_stream.h\n    pod5_format/io_manager.h\n    pod5_format/memory_pool.h\n    pod5_format/result.h\n    pod5_format/dictionary_writer.h\n    pod5_format/schema_field_builder.h\n    pod5_format/schema_utils.h\n    pod5_format/table_reader.h\n    pod5_format/thread_pool.h\n    pod5_format/tuple_utils.h\n    pod5_format/types.h\n\n\n    ${CMAKE_CURRENT_BINARY_DIR}/pod5_format/pod5_format_export.h\n)\n\nset(svb16_headers\n    pod5_format/svb16/svb16.h\n    pod5_format/svb16/common.hpp\n    pod5_format/svb16/decode.hpp\n    pod5_format/svb16/decode_scalar.hpp\n    pod5_format/svb16/decode_x64.hpp\n    pod5_format/svb16/encode.hpp\n    pod5_format/svb16/encode_scalar.hpp\n    pod5_format/svb16/encode_x64.hpp\n    pod5_format/svb16/intrinsics.hpp\n    pod5_format/svb16/shuffle_tables.hpp\n    pod5_format/svb16/simd_detect_x64.hpp\n)\n\nset_target_properties(pod5_format\n    PROPERTIES\n        POSITION_INDEPENDENT_CODE 1\n        CXX_STANDARD 20\n        PUBLIC_HEADER \"${public_headers}\"\n)\n\n# Link these libraries publicly when doing a static lib build\nset(maybe_public_libs\n    arrow::arrow\n    flatbuffers::flatbuffers\n)\n\nif (BUILD_SHARED_LIB)\n    target_link_libraries(pod5_format PRIVATE ${maybe_public_libs})\nelse()\n    target_link_libraries(pod5_format PUBLIC ${maybe_public_libs})\nendif()\n\ntarget_link_libraries(pod5_format\n    PRIVATE\n        pod5_flatbuffers\n        zstd::zstd\n        ZLIB::ZLIB\n        Threads::Threads\n)\n\nif(APPLE)\n    find_library(CORE_FOUNDATION CoreFoundation)\n    target_link_libraries(pod5_format PRIVATE ${CORE_FOUNDATION})\nendif()\n\ntarget_include_directories(pod5_format\n    PUBLIC\n        ${CMAKE_CURRENT_SOURCE_DIR}\n        ${CMAKE_CURRENT_BINARY_DIR}\n)\n\nflatbuffers_generate_headers(\n    TARGET pod5_flatbuffers\n    SCHEMAS\n        pod5_format/flatbuffers/footer.fbs\n    INCLUDE_PREFIX \"\"\n    FLAGS --cpp\n)\n\nif (NOT MSVC)\n    set(pod5_warning_options -Werror -Wall -Wno-comment -Wno-error=deprecated-declarations -Wno-deprecated-declarations)\n    target_compile_options(pod5_format PRIVATE ${pod5_warning_options})\nendif()\ngenerate_export_header(pod5_format EXPORT_FILE_NAME pod5_format/pod5_format_export.h)\n\ninstall(\n    TARGETS pod5_format\n    PUBLIC_HEADER DESTINATION \"include/pod5_format\"\n)\n\ninstall(\n    FILES ${svb16_headers}\n    DESTINATION \"include/pod5_format/svb16\"\n)\n\nif (POD5_BUILD_EXAMPLES)\n    add_subdirectory(examples)\nendif()\nif (NOT POD5_DISABLE_TESTS)\n    add_subdirectory(test)\nendif()\n\nif (BUILD_PYTHON_WHEEL)\n    add_subdirectory(pod5_format_pybind)\nendif()\n"
  },
  {
    "path": "c++/examples/CMakeLists.txt",
    "content": "add_executable(find_all_read_ids\n    find_all_read_ids.cpp\n)\n\ntarget_link_libraries(find_all_read_ids\n    pod5_format\n)\n# Needs C++17 to use pod5_format/uuid.h\nset_target_properties(find_all_read_ids PROPERTIES CXX_STANDARD 17)\n\nadd_executable(find_specific_read_ids\n    find_specific_read_ids.cpp\n)\n\ntarget_link_libraries(find_specific_read_ids\n    pod5_format\n)\n# Needs C++17 to use pod5_format/uuid.h\nset_target_properties(find_specific_read_ids PROPERTIES CXX_STANDARD 17)\n\nadd_executable(find_all_read_data\n    find_all_read_data.cpp\n)\n\ntarget_link_libraries(find_all_read_data\n    pod5_format\n)\n# Needs C++17 to use pod5_format/uuid.h\nset_target_properties(find_all_read_data PROPERTIES CXX_STANDARD 17)\n\nadd_executable(find_specific_read_ids_with_signal\n    find_specific_read_ids_with_signal.cpp\n)\n\ntarget_link_libraries(find_specific_read_ids_with_signal\n    pod5_format\n)\n# Needs C++17 to use pod5_format/uuid.h\nset_target_properties(find_specific_read_ids_with_signal PROPERTIES CXX_STANDARD 17)\n"
  },
  {
    "path": "c++/examples/README.md",
    "content": "C++ Examples\n============\n\nThese examples use the POD5 C API to read file data, they are written using C++.\n\nfind_all_read_ids\n-----------------\n\nFind all the read ids in a given pod5 file, and save their read id to a text file.\n\nfind_specific_read_ids\n----------------------\n\nFind specific read ids in a given pod5 file, and save their read number to a text file.\n"
  },
  {
    "path": "c++/examples/find_all_read_data.cpp",
    "content": "#include \"pod5_format/c_api.h\"\n#include \"pod5_format/uuid.h\"\n\n#include <array>\n#include <fstream>\n#include <iostream>\n#include <vector>\n\nint main(int argc, char ** argv)\n{\n    if (argc != 2) {\n        std::cerr << \"Expected one argument - an pod5 file to search\\n\";\n        return EXIT_FAILURE;\n    }\n\n    // Initialise the POD5 library:\n    pod5_init();\n\n    // Open the file ready for walking:\n    Pod5FileReader_t * file = pod5_open_file(argv[1]);\n    if (!file) {\n        std::cerr << \"Failed to open file \" << argv[1] << \": \" << pod5_get_error_string() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    std::size_t batch_count = 0;\n    if (pod5_get_read_batch_count(&batch_count, file) != POD5_OK) {\n        std::cerr << \"Failed to query batch count: \" << pod5_get_error_string() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    std::size_t read_count = 0;\n\n    for (std::size_t batch_index = 0; batch_index < batch_count; ++batch_index) {\n        std::cout << \"batch_index: \" << batch_index + 1 << \"/\" << batch_count << \"\\n\";\n\n        Pod5ReadRecordBatch_t * batch = nullptr;\n        if (pod5_get_read_batch(&batch, file, batch_index) != POD5_OK) {\n            std::cerr << \"Failed to get batch: \" << pod5_get_error_string() << \"\\n\";\n            return EXIT_FAILURE;\n        }\n\n        std::size_t batch_row_count = 0;\n        if (pod5_get_read_batch_row_count(&batch_row_count, batch) != POD5_OK) {\n            std::cerr << \"Failed to get batch row count\\n\";\n            return EXIT_FAILURE;\n        }\n\n        for (std::size_t row = 0; row < batch_row_count; ++row) {\n            uint16_t read_table_version = 0;\n            ReadBatchRowInfo_t read_data;\n            if (pod5_get_read_batch_row_info_data(\n                    batch, row, READ_BATCH_ROW_INFO_VERSION, &read_data, &read_table_version)\n                != POD5_OK)\n            {\n                std::cerr << \"Failed to get read \" << row << \": \" << pod5_get_error_string()\n                          << \"\\n\";\n                return EXIT_FAILURE;\n            }\n\n            read_count += 1;\n\n            std::size_t sample_count = 0;\n            pod5_get_read_complete_sample_count(file, batch, row, &sample_count);\n\n            std::vector<std::int16_t> samples;\n            samples.resize(sample_count);\n            pod5_get_read_complete_signal(file, batch, row, samples.size(), samples.data());\n\n            // Run info\n            RunInfoDictData_t * run_info = nullptr;\n            if (pod5_get_run_info(batch, read_data.run_info, &run_info) != POD5_OK) {\n                std::cerr << \"Failed to get run info \" + std::to_string(read_data.run_info) + \" : \"\n                                 + pod5_get_error_string()\n                          << \"\\n\";\n                return EXIT_FAILURE;\n            }\n\n            pod5_free_run_info(run_info);\n        }\n\n        if (pod5_free_read_batch(batch) != POD5_OK) {\n            std::cerr << \"Failed to release batch\\n\";\n            return EXIT_FAILURE;\n        }\n    }\n\n    std::cout << \"Extracted \" << read_count << \" reads \"\n              << \"\\n\";\n\n    // Close the reader\n    if (pod5_close_and_free_reader(file) != POD5_OK) {\n        std::cerr << \"Failed to close reader: \" << pod5_get_error_string() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    // Cleanup the library\n    pod5_terminate();\n}\n"
  },
  {
    "path": "c++/examples/find_all_read_ids.cpp",
    "content": "#include \"pod5_format/c_api.h\"\n#include \"pod5_format/uuid.h\"\n\n#include <array>\n#include <fstream>\n#include <iostream>\n#include <vector>\n\nint main(int argc, char ** argv)\n{\n    if (argc != 2) {\n        std::cerr << \"Expected one argument - an pod5 file to search\\n\";\n        return EXIT_FAILURE;\n    }\n\n    // Initialise the POD5 library:\n    pod5_init();\n\n    // Open the file ready for walking:\n    Pod5FileReader_t * file = pod5_open_file(argv[1]);\n    if (!file) {\n        std::cerr << \"Failed to open file \" << argv[1] << \": \" << pod5_get_error_string() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    std::size_t batch_count = 0;\n    if (pod5_get_read_batch_count(&batch_count, file) != POD5_OK) {\n        std::cerr << \"Failed to query batch count: \" << pod5_get_error_string() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    std::string output_path(\"read_ids.txt\");\n    std::cout << \"Writing read ids to \" << output_path << \"\\n\";\n    std::ofstream output_stream(output_path);\n\n    std::size_t read_count = 0;\n\n    for (std::size_t batch_index = 0; batch_index < batch_count; ++batch_index) {\n        Pod5ReadRecordBatch_t * batch = nullptr;\n        if (pod5_get_read_batch(&batch, file, batch_index) != POD5_OK) {\n            std::cerr << \"Failed to get batch: \" << pod5_get_error_string() << \"\\n\";\n            return EXIT_FAILURE;\n        }\n\n        std::size_t batch_row_count = 0;\n        if (pod5_get_read_batch_row_count(&batch_row_count, batch) != POD5_OK) {\n            std::cerr << \"Failed to get batch row count\\n\";\n            return EXIT_FAILURE;\n        }\n\n        for (std::size_t row = 0; row < batch_row_count; ++row) {\n            uint16_t read_table_version = 0;\n            ReadBatchRowInfo_t read_data;\n            if (pod5_get_read_batch_row_info_data(\n                    batch, row, READ_BATCH_ROW_INFO_VERSION, &read_data, &read_table_version)\n                != POD5_OK)\n            {\n                std::cerr << \"Failed to get read \" << row << \"\\n\";\n                return EXIT_FAILURE;\n            }\n\n            std::array<char, 37> formatted_read_id;\n            pod5_format_read_id(read_data.read_id, formatted_read_id.data());\n            output_stream << formatted_read_id.data() << \"\\n\";\n            read_count += 1;\n\n            std::size_t sample_count = 0;\n            pod5_get_read_complete_sample_count(file, batch, row, &sample_count);\n\n            std::vector<std::int16_t> samples;\n            samples.resize(sample_count);\n            pod5_get_read_complete_signal(file, batch, row, samples.size(), samples.data());\n        }\n\n        if (pod5_free_read_batch(batch) != POD5_OK) {\n            std::cerr << \"Failed to release batch\\n\";\n            return EXIT_FAILURE;\n        }\n    }\n\n    std::cout << \"Extracted \" << read_count << \" read ids into \" << output_path << \"\\n\";\n\n    // Close the reader\n    if (pod5_close_and_free_reader(file) != POD5_OK) {\n        std::cerr << \"Failed to close reader: \" << pod5_get_error_string() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    // Cleanup the library\n    pod5_terminate();\n}\n"
  },
  {
    "path": "c++/examples/find_specific_read_ids.cpp",
    "content": "#include \"pod5_format/c_api.h\"\n#include \"pod5_format/uuid.h\"\n\n#include <fstream>\n#include <iostream>\n#include <vector>\n\nint main(int argc, char ** argv)\n{\n    if (argc != 3) {\n        std::cerr << \"Expected two arguments:\\n\"\n                  << \" - an pod5 file to search\\n\"\n                  << \" - a file containing newline separated of read ids\\n\";\n        return EXIT_FAILURE;\n    }\n\n    // Initialise the POD5 library:\n    pod5_init();\n\n    // Open the file ready for walking:\n    Pod5FileReader_t * file = pod5_open_file(argv[1]);\n    if (!file) {\n        std::cerr << \"Failed to open file \" << argv[1] << \": \" << pod5_get_error_string() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    std::size_t batch_count = 0;\n    if (pod5_get_read_batch_count(&batch_count, file) != POD5_OK) {\n        std::cerr << \"Failed to query batch count: \" << pod5_get_error_string() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    std::vector<pod5::Uuid> search_uuids;\n    std::string input_path(argv[2]);\n    try {\n        std::cout << \"Reading input read ids from \" << input_path << \"\\n\";\n        std::string line;\n        std::ifstream input_stream(input_path);\n        while (std::getline(input_stream, line)) {\n            auto const uuid = pod5::Uuid::from_string(line);\n            if (!uuid) {\n                std::cerr << '\"' << line << \"\\\" is not a valid UUID, ignoring it\\n\";\n            } else {\n                search_uuids.push_back(*uuid);\n            }\n        }\n        std::cout << \"  Read \" << search_uuids.size() << \" ids from the text file\\n\";\n    } catch (std::exception const & e) {\n        std::cerr << \"Failed to parse UUID values from \" << input_path << \": \" << e.what() << \"\\n\";\n    }\n\n    std::string output_path(\"read_ids.txt\");\n    std::cout << \"Writing selected read numbers to \" << output_path << \"\\n\";\n    std::ofstream output_stream(output_path);\n\n    // Plan the most efficient route through the file for the required read ids:\n    std::vector<std::uint32_t> traversal_batch_counts(batch_count);\n    std::vector<std::uint32_t> traversal_row_indices(search_uuids.size());\n    std::size_t find_success_count = 0;\n    if (pod5_plan_traversal(\n            file,\n            (uint8_t *)search_uuids.data(),\n            search_uuids.size(),\n            traversal_batch_counts.data(),\n            traversal_row_indices.data(),\n            &find_success_count)\n        != POD5_OK)\n    {\n        std::cerr << \"Failed to plan traversal of file: \" << pod5_get_error_string() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    if (find_success_count != search_uuids.size()) {\n        std::cerr << \"Failed to find \" << (search_uuids.size() - find_success_count) << \" reads\\n\";\n    }\n\n    std::size_t read_count = 0;\n    std::size_t row_offset = 0;\n\n    // Walk the suggested traversal route, storing read data.\n    for (std::size_t batch_index = 0; batch_index < batch_count; ++batch_index) {\n        Pod5ReadRecordBatch_t * batch = nullptr;\n        if (pod5_get_read_batch(&batch, file, batch_index) != POD5_OK) {\n            std::cerr << \"Failed to get batch: \" << pod5_get_error_string() << \"\\n\";\n            return EXIT_FAILURE;\n        }\n\n        std::cout << \"Processing batch \" << (batch_index + 1) << \" of \" << batch_count << \"\\n\";\n        for (std::size_t row_index = 0; row_index < traversal_batch_counts[batch_index];\n             ++row_index)\n        {\n            std::uint32_t batch_row = traversal_row_indices[row_index + row_offset];\n\n            uint16_t read_table_version = 0;\n            ReadBatchRowInfo_t read_data;\n            if (pod5_get_read_batch_row_info_data(\n                    batch, batch_row, READ_BATCH_ROW_INFO_VERSION, &read_data, &read_table_version)\n                != POD5_OK)\n            {\n                std::cerr << \"Failed to get read \" << batch_row << \"\\n\";\n                return EXIT_FAILURE;\n            }\n\n            output_stream << read_data.read_number << \"\\n\";\n            read_count += 1;\n        }\n        row_offset += traversal_batch_counts[batch_index];\n\n        if (pod5_free_read_batch(batch) != POD5_OK) {\n            std::cerr << \"Failed to release batch\\n\";\n            return EXIT_FAILURE;\n        }\n    }\n\n    std::cout << \"Extracted \" << read_count << \" read numbers into \" << output_path << \"\\n\";\n\n    // Close the reader\n    if (pod5_close_and_free_reader(file) != POD5_OK) {\n        std::cerr << \"Failed to close reader: \" << pod5_get_error_string() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    // Cleanup the library\n    pod5_terminate();\n}\n"
  },
  {
    "path": "c++/examples/find_specific_read_ids_with_signal.cpp",
    "content": "#include \"pod5_format/c_api.h\"\n#include \"pod5_format/uuid.h\"\n\n#include <fstream>\n#include <iostream>\n#include <vector>\n\nint main(int argc, char ** argv)\n{\n    if (argc != 3) {\n        std::cerr << \"Expected two arguments:\\n\"\n                  << \" - an pod5 file to search\\n\"\n                  << \" - a file containing newline separated of read ids\\n\";\n        return EXIT_FAILURE;\n    }\n\n    // Initialise the POD5 library:\n    pod5_init();\n\n    // Open the file ready for walking:\n    Pod5FileReader_t * file = pod5_open_file(argv[1]);\n    if (!file) {\n        std::cerr << \"Failed to open file \" << argv[1] << \": \" << pod5_get_error_string() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    std::size_t batch_count = 0;\n    if (pod5_get_read_batch_count(&batch_count, file) != POD5_OK) {\n        std::cerr << \"Failed to query batch count: \" << pod5_get_error_string() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    std::vector<pod5::Uuid> search_uuids;\n    std::string input_path(argv[2]);\n    try {\n        std::cout << \"Reading input read ids from \" << input_path << \"\\n\";\n        std::string line;\n        std::ifstream input_stream(input_path);\n        while (std::getline(input_stream, line)) {\n            auto const uuid = pod5::Uuid::from_string(line);\n            if (!uuid) {\n                std::cerr << '\"' << line << \"\\\" is not a valid UUID, ignoring it\\n\";\n            } else {\n                search_uuids.push_back(*uuid);\n            }\n        }\n        std::cout << \"  Read \" << search_uuids.size() << \" ids from the text file\\n\";\n    } catch (std::exception const & e) {\n        std::cerr << \"Failed to parse UUID values from \" << input_path << \": \" << e.what() << \"\\n\";\n    }\n\n    std::string output_path(\"read_ids.txt\");\n    std::cout << \"Writing selected read numbers to \" << output_path << \"\\n\";\n    std::ofstream output_stream(output_path);\n\n    // Plan the most efficient route through the file for the required read ids:\n    std::vector<std::uint32_t> traversal_batch_counts(batch_count);\n    std::vector<std::uint32_t> traversal_row_indices(search_uuids.size());\n    std::size_t find_success_count = 0;\n    if (pod5_plan_traversal(\n            file,\n            (uint8_t *)search_uuids.data(),\n            search_uuids.size(),\n            traversal_batch_counts.data(),\n            traversal_row_indices.data(),\n            &find_success_count)\n        != POD5_OK)\n    {\n        std::cerr << \"Failed to plan traversal of file: \" << pod5_get_error_string() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    if (find_success_count != search_uuids.size()) {\n        std::cerr << \"Failed to find \" << (search_uuids.size() - find_success_count) << \" reads\\n\";\n    }\n\n    std::size_t read_count = 0;\n    std::size_t samples_read = 0;\n\n    std::size_t row_offset = 0;\n\n    // Walk the suggested traversal route, storing read data.\n    for (std::size_t batch_index = 0; batch_index < batch_count; ++batch_index) {\n        Pod5ReadRecordBatch_t * batch = nullptr;\n        if (pod5_get_read_batch(&batch, file, batch_index) != POD5_OK) {\n            std::cerr << \"Failed to get batch: \" << pod5_get_error_string() << \"\\n\";\n            return EXIT_FAILURE;\n        }\n\n        std::cout << \"Processing batch \" << (batch_index + 1) << \" of \" << batch_count << \"\\n\";\n        for (std::size_t row_index = 0; row_index < traversal_batch_counts[batch_index];\n             ++row_index)\n        {\n            std::uint32_t batch_row = traversal_row_indices[row_index + row_offset];\n\n            uint16_t read_table_version = 0;\n            ReadBatchRowInfo_t read_data;\n            if (pod5_get_read_batch_row_info_data(\n                    batch, batch_row, READ_BATCH_ROW_INFO_VERSION, &read_data, &read_table_version)\n                != POD5_OK)\n            {\n                std::cerr << \"Failed to get read \" << batch_row << \"\\n\";\n                return EXIT_FAILURE;\n            }\n\n            std::size_t sample_count = 0;\n            pod5_get_read_complete_sample_count(file, batch, batch_row, &sample_count);\n\n            std::vector<std::int16_t> samples;\n            samples.resize(sample_count);\n            pod5_get_read_complete_signal(file, batch, batch_row, samples.size(), samples.data());\n\n            std::int64_t samples_sum = 0;\n            for (std::size_t i = 0; i < samples.size(); ++i) {\n                samples_sum += samples[i];\n            }\n\n            output_stream << read_data.calibration_offset << \" \" << read_data.calibration_scale\n                          << \" \" << samples_sum << \"\\n\";\n            read_count += 1;\n            samples_read += samples.size();\n        }\n        row_offset += traversal_batch_counts[batch_index];\n\n        if (pod5_free_read_batch(batch) != POD5_OK) {\n            std::cerr << \"Failed to release batch\\n\";\n            return EXIT_FAILURE;\n        }\n    }\n\n    std::cout << \"Extracted \" << read_count << \" reads and \" << samples_read << \" samples into \"\n              << output_path << \"\\n\";\n\n    // Close the reader\n    if (pod5_close_and_free_reader(file) != POD5_OK) {\n        std::cerr << \"Failed to close reader: \" << pod5_get_error_string() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    // Cleanup the library\n    pod5_terminate();\n}\n"
  },
  {
    "path": "c++/pod5_format/async_signal_loader.cpp",
    "content": "#include \"pod5_format/async_signal_loader.h\"\n\nnamespace pod5 {\n\nstd::size_t const AsyncSignalLoader::MINIMUM_JOB_SIZE = 50;\n\nAsyncSignalLoader::AsyncSignalLoader(\n    std::shared_ptr<pod5::FileReader> const & reader,\n    SamplesMode samples_mode,\n    gsl::span<std::uint32_t const> const & batch_counts,\n    gsl::span<std::uint32_t const> const & batch_rows,\n    std::size_t worker_count,\n    std::size_t max_pending_batches)\n: m_reader(reader)\n, m_samples_mode(samples_mode)\n, m_max_pending_batches(max_pending_batches)\n, m_reads_batch_count(m_reader->num_read_record_batches())\n, m_batch_counts(batch_counts)\n, m_total_batch_count_so_far(0)\n, m_batch_rows(batch_rows)\n, m_worker_job_size(\n      std::max<std::size_t>(\n          MINIMUM_JOB_SIZE,\n          m_batch_rows.size() / (m_reads_batch_count * worker_count * 2)))\n, m_current_batch(0)\n, m_finished(false)\n, m_has_error(false)\n, m_batches_size(0)\n{\n    // Setup first batch:\n    {\n        std::unique_lock<std::mutex> l(m_worker_sync);\n        auto setup_result = setup_next_in_progress_batch(l);\n        if (!setup_result.ok()) {\n            set_error(setup_result);\n        }\n    }\n\n    // Kick off workers on jobs:\n    for (std::size_t i = 0; i < worker_count; ++i) {\n        m_workers.emplace_back([&] { run_worker(); });\n    }\n}\n\nAsyncSignalLoader::~AsyncSignalLoader()\n{\n    m_finished = true;\n    // Wait for all workers to complete:\n    for (std::size_t i = 0; i < m_workers.size(); ++i) {\n        m_workers[i].join();\n    }\n}\n\nResult<std::unique_ptr<CachedBatchSignalData>> AsyncSignalLoader::release_next_batch(\n    std::optional<std::chrono::steady_clock::time_point> timeout)\n{\n    std::shared_ptr<SignalCacheWorkPackage> batch;\n\n    // Return any error, if one has occurred:\n    if (m_has_error) {\n        return error();\n    }\n\n    // First wait until there is a batch available:\n    do {\n        std::unique_lock<std::mutex> l(m_batches_sync);\n        // Wait until there is a batch available:\n        m_batch_done.wait_until(\n            l, timeout.value_or(std::chrono::steady_clock::now() + std::chrono::seconds(5)), [&] {\n                return m_batches.size() || m_finished || m_has_error;\n            });\n\n        // Grab a batch if one exists (note error or user destroying us might have happened instead):\n        if (!m_batches.empty()) {\n            batch = std::move(m_batches.front());\n            assert(batch);\n            m_batches.pop_front();\n            m_batches_size -= 1;\n            break;\n        }\n\n        if (timeout && std::chrono::steady_clock::now() > *timeout) {\n            return nullptr;\n        }\n    } while (!m_finished && !m_has_error);\n\n    // Return any error, if one has occurred during our wait:\n    if (m_has_error) {\n        return error();\n    }\n\n    // If we got a batch, wait for all work to be finished, then return it:\n    if (batch) {\n        // Wait if we are ahead of the loader:\n        while (!batch->is_complete()) {\n            std::this_thread::sleep_for(std::chrono::milliseconds(1));\n        }\n\n        return batch->release_data();\n    }\n\n    // No more data - return null.\n    return nullptr;\n}\n\nvoid AsyncSignalLoader::set_error(pod5::Status status)\n{\n    assert(!status.ok());\n    {\n        std::lock_guard<std::mutex> l{m_error_mutex};\n        m_error = std::move(status);\n    }\n    m_has_error = true;\n}\n\npod5::Status AsyncSignalLoader::error() const\n{\n    std::lock_guard<std::mutex> l{m_error_mutex};\n    return m_error;\n}\n\nvoid AsyncSignalLoader::run_worker()\n{\n    // Continue to work while there is work to do, and no error has occurred\n    while (!m_finished && !m_has_error) {\n        std::shared_ptr<SignalCacheWorkPackage> batch;\n        std::uint32_t row_start = 0;\n\n        // Try to secure some new work:\n        {\n            std::unique_lock<std::mutex> l(m_worker_sync);\n            // If we have run out of batches to process, release anything in progress and return:\n            if (m_current_batch >= m_reads_batch_count) {\n                release_in_progress_batch();\n                break;\n            }\n\n            // If we have more batches than asked for complete that have\n            // not been queried, wait for it to get taken:\n            if (m_batches_size > m_max_pending_batches) {\n                l.unlock();\n                std::this_thread::sleep_for(std::chrono::milliseconds(10));\n                continue;\n            }\n\n            // Now, if we have no work left in the current batch, release that:\n            if (!m_in_progress_batch->has_work_left()) {\n                if (!m_batch_counts.empty()) {\n                    m_total_batch_count_so_far += m_batch_counts[m_current_batch];\n                }\n\n                // Release the current batch:\n                release_in_progress_batch();\n\n                // Then try to setup the next batch, if one exists:\n                m_current_batch += 1;\n                if (m_current_batch >= m_reads_batch_count) {\n                    // No more work to do.\n                    m_finished = true;\n                    break;\n                }\n\n                auto setup_result = setup_next_in_progress_batch(l);\n                if (!setup_result.ok()) {\n                    set_error(setup_result);\n                    return;\n                }\n            }\n\n            // Finally, tell the work package we have secured we are starting to do some work:\n            batch = m_in_progress_batch;\n            row_start = m_in_progress_batch->start_rows(l, m_worker_job_size);\n        }\n\n        // Now execute the work, for all the rows we said we would:\n        std::uint32_t const row_end =\n            std::min(row_start + m_worker_job_size, batch->job_row_count());\n\n        do_work(batch, row_start, row_end);\n\n        // And report the work completed for anyone waiting:\n        batch->complete_rows(m_worker_job_size);\n    }\n}\n\nvoid AsyncSignalLoader::do_work(\n    std::shared_ptr<SignalCacheWorkPackage> const & batch,\n    std::uint32_t row_start,\n    std::uint32_t row_end)\n{\n    // First secure the sample counts column for the batch we are processing:\n    auto signal_column = batch->read_batch().signal_column();\n\n    // And record where we are starting in the batch rows array, if it exists:\n    for (std::uint32_t i = row_start; i < row_end; ++i) {\n        // Find the actual batch row to query - we may be working on a subset of batch data:\n        auto const actual_batch_row = batch->get_batch_row_to_query(i);\n        // Get the signal row data for the read:\n        auto const signal_rows = std::static_pointer_cast<arrow::UInt64Array>(\n            signal_column->value_slice(actual_batch_row));\n        auto const signal_rows_span =\n            gsl::make_span(signal_rows->raw_values(), signal_rows->length());\n\n        // Find the sample count for these rows:\n        auto sample_count_result = m_reader->extract_sample_count(signal_rows_span);\n        if (!sample_count_result.ok()) {\n            set_error(sample_count_result.status());\n            return;\n        }\n        std::uint64_t sample_count = *sample_count_result;\n\n        // And query the samples if that has been requested:\n        std::vector<std::int16_t> samples;\n        if (m_samples_mode == SamplesMode::Samples) {\n            samples.resize(sample_count);\n            auto samples_result =\n                m_reader->extract_samples(signal_rows_span, gsl::make_span(samples));\n            if (!samples_result.ok()) {\n                set_error(std::move(samples_result));\n                return;\n            }\n            sample_count = samples.size();\n        }\n\n        // Store the queried data into the batch:\n        batch->set_samples(i, sample_count, std::move(samples));\n    }\n}\n\nStatus AsyncSignalLoader::setup_next_in_progress_batch(std::unique_lock<std::mutex> & lock)\n{\n    assert(!m_in_progress_batch);\n    ARROW_ASSIGN_OR_RAISE(auto read_batch, m_reader->read_read_record_batch(m_current_batch));\n    std::size_t row_count = read_batch.num_rows();\n\n    gsl::span<std::uint32_t const> next_specific_batch_rows;\n    if (!m_batch_counts.empty()) {\n        row_count = m_batch_counts[m_current_batch];\n        if (!m_batch_rows.empty()) {\n            next_specific_batch_rows = m_batch_rows.subspan(m_total_batch_count_so_far, row_count);\n        }\n    }\n\n    m_in_progress_batch = std::make_shared<SignalCacheWorkPackage>(\n        m_current_batch, row_count, next_specific_batch_rows, std::move(read_batch));\n    return Status::OK();\n}\n\nvoid AsyncSignalLoader::release_in_progress_batch()\n{\n    if (m_in_progress_batch) {\n        assert(!m_in_progress_batch->has_work_left());\n        std::lock_guard<std::mutex> l(m_batches_sync);\n        m_batches.emplace_back(std::move(m_in_progress_batch));\n        m_batches_size += 1;\n        m_batch_done.notify_all();\n    }\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/async_signal_loader.h",
    "content": "#pragma once\n\n#include \"pod5_format/file_reader.h\"\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/read_table_reader.h\"\n#include \"pod5_format/signal_table_reader.h\"\n\n#include <arrow/array/array_nested.h>\n#include <arrow/array/array_primitive.h>\n\n#include <condition_variable>\n#include <deque>\n#include <memory>\n#include <mutex>\n#include <optional>\n#include <thread>\n\nnamespace pod5 {\n\nclass POD5_FORMAT_EXPORT CachedBatchSignalData {\npublic:\n    CachedBatchSignalData(std::uint32_t batch_index, std::size_t entry_count)\n    : m_batch_index(batch_index)\n    , m_sample_counts(entry_count)\n    , m_samples(entry_count)\n    {\n    }\n\n    std::uint32_t batch_index() const { return m_batch_index; }\n\n    /// Find a list of sample counts for all requested batch rows.\n    std::vector<std::uint64_t> const & sample_count() const { return m_sample_counts; }\n\n    /// Find a list of signal samples counts for all requested batch rows.\n    std::vector<std::vector<std::int16_t>> const & samples() const { return m_samples; }\n\n    void\n    set_samples(std::size_t row, std::uint64_t sample_count, std::vector<std::int16_t> && samples)\n    {\n        m_sample_counts[row] = sample_count;\n        m_samples[row] = std::move(samples);\n    }\n\nprivate:\n    std::uint32_t m_batch_index;\n    std::vector<std::uint64_t> m_sample_counts;\n    std::vector<std::vector<std::int16_t>> m_samples;\n};\n\nclass POD5_FORMAT_EXPORT SignalCacheWorkPackage {\npublic:\n    SignalCacheWorkPackage(\n        std::uint32_t batch_index,\n        std::size_t job_row_count,\n        gsl::span<std::uint32_t const> const & specific_job_rows,\n        pod5::ReadTableRecordBatch && read_batch)\n    : m_job_row_count(job_row_count)\n    , m_specific_job_rows(specific_job_rows)\n    , m_next_row_to_start(0)\n    , m_completed_rows(0)\n    , m_cached_data(std::make_unique<CachedBatchSignalData>(batch_index, m_job_row_count))\n    , m_read_batch(std::move(read_batch))\n    {\n    }\n\n    std::uint32_t job_row_count() const { return m_job_row_count; }\n\n    void\n    set_samples(std::size_t row, std::uint64_t sample_count, std::vector<std::int16_t> && samples)\n    {\n        m_cached_data->set_samples(row, sample_count, std::move(samples));\n    }\n\n    std::unique_ptr<CachedBatchSignalData> release_data() { return std::move(m_cached_data); }\n\n    pod5::ReadTableRecordBatch const & read_batch() const { return m_read_batch; }\n\n    // Find the actual batch row to query, for a given job row index.\n    std::uint32_t get_batch_row_to_query(std::uint32_t job_row_index) const\n    {\n        // We allow the caller to specify a subset of batch rows to iterate:\n        if (!m_specific_job_rows.empty()) {\n            return m_specific_job_rows[job_row_index];\n        }\n\n        return job_row_index;\n    }\n\n    std::uint32_t start_rows(std::unique_lock<std::mutex> & l, std::size_t row_count)\n    {\n        auto row = m_next_row_to_start;\n        m_next_row_to_start += row_count;\n        return row;\n    }\n\n    void complete_rows(std::uint32_t row_count) { m_completed_rows += row_count; }\n\n    bool has_work_left() const { return m_next_row_to_start < m_job_row_count; }\n\n    bool is_complete() const { return m_completed_rows.load() >= m_job_row_count; }\n\nprivate:\n    std::size_t m_job_row_count;\n    gsl::span<std::uint32_t const> m_specific_job_rows;\n\n    std::uint32_t m_next_row_to_start;\n    std::atomic<std::uint32_t> m_completed_rows;\n\n    std::unique_ptr<CachedBatchSignalData> m_cached_data;\n    pod5::ReadTableRecordBatch m_read_batch;\n};\n\nclass POD5_FORMAT_EXPORT AsyncSignalLoader {\npublic:\n    // Minimum number of tasks one thread will do in a batch.\n    static std::size_t const MINIMUM_JOB_SIZE;\n    enum class SamplesMode {\n        NoSamples,\n        Samples,\n    };\n\n    AsyncSignalLoader(\n        std::shared_ptr<pod5::FileReader> const & reader,\n        SamplesMode samples_mode,\n        gsl::span<std::uint32_t const> const & batch_counts,\n        gsl::span<std::uint32_t const> const & batch_rows,\n        std::size_t worker_count = std::thread::hardware_concurrency(),\n        std::size_t max_pending_batches = 10);\n\n    ~AsyncSignalLoader();\n\n    /// Find if all work is complete in the loader.\n    bool is_finished() const { return m_finished; }\n\n    /// Get the next batch of loaded signal, always returns the consecutive next signal batch\n    /// \\note Returns nullptr when timeoout occurs, or if all data is exhausted.\n    Result<std::unique_ptr<CachedBatchSignalData>> release_next_batch(\n        std::optional<std::chrono::steady_clock::time_point> timeout = std::nullopt);\n\nprivate:\n    /// Set an error code that will stop all async loading and return an error to the caller.\n    void set_error(pod5::Status status);\n    pod5::Status error() const;\n\n    void run_worker();\n    void do_work(\n        std::shared_ptr<SignalCacheWorkPackage> const & batch,\n        std::uint32_t row_start,\n        std::uint32_t row_end);\n\n    /// Setup a new batch for in progress work to contain.\n    /// \\param lock A lock held on m_worker_sync.\n    /// \\note There must not be a batch already in progress.\n    /// \\note m_current_batch is used as the index of the next batch to begin.\n    Status setup_next_in_progress_batch(std::unique_lock<std::mutex> & lock);\n\n    /// Release the currently in progress batch to readers, if it exists.\n    /// \\note This call locks m_batches_sync internally.\n    /// \\note The batch must not have any work remaining to start, but can be completing already started work.\n    /// \\note This call notifys the condition variable to alert readers that new data is available.\n    void release_in_progress_batch();\n\n    std::shared_ptr<pod5::FileReader> m_reader;\n    SamplesMode m_samples_mode;\n    std::size_t m_max_pending_batches;\n    std::size_t m_reads_batch_count;\n    gsl::span<std::uint32_t const> m_batch_counts;\n    std::size_t m_total_batch_count_so_far;\n    gsl::span<std::uint32_t const> m_batch_rows;\n\n    std::uint32_t const m_worker_job_size;\n\n    std::mutex m_worker_sync;\n    std::condition_variable m_batch_done;\n    std::uint32_t m_current_batch;\n\n    std::atomic<bool> m_finished;\n    std::atomic<bool> m_has_error;\n    mutable std::mutex m_error_mutex;\n    pod5::Status m_error;\n    std::shared_ptr<SignalCacheWorkPackage> m_in_progress_batch;\n\n    std::mutex m_batches_sync;\n    std::atomic<std::uint32_t> m_batches_size;\n    std::deque<std::shared_ptr<SignalCacheWorkPackage>> m_batches;\n\n    std::vector<std::thread> m_workers;\n};\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/c_api.cpp",
    "content": "#include \"pod5_format/c_api.h\"\n\n#include \"pod5_format/file_reader.h\"\n#include \"pod5_format/file_writer.h\"\n#include \"pod5_format/read_table_reader.h\"\n#include \"pod5_format/signal_compression.h\"\n#include \"pod5_format/signal_table_reader.h\"\n#include \"pod5_format/uuid.h\"\n\n#include <arrow/array/array_binary.h>\n#include <arrow/array/array_dict.h>\n#include <arrow/array/array_nested.h>\n#include <arrow/array/array_primitive.h>\n#include <arrow/memory_pool.h>\n#include <arrow/type.h>\n\n#include <limits>\n#include <optional>\n\n//---------------------------------------------------------------------------------------------------------------------\nstruct Pod5FileReader {\n    Pod5FileReader(std::shared_ptr<pod5::FileReader> && reader_) : reader(std::move(reader_)) {}\n\n    std::shared_ptr<pod5::FileReader const> reader;\n};\n\nstruct Pod5FileWriter {\n    Pod5FileWriter(std::unique_ptr<pod5::FileWriter> && writer_) : writer(std::move(writer_)) {}\n\n    std::unique_ptr<pod5::FileWriter> writer;\n};\n\nstruct Pod5ReadRecordBatch {\n    Pod5ReadRecordBatch(\n        pod5::ReadTableRecordBatch && batch_,\n        std::shared_ptr<pod5::FileReader const> reader)\n    : batch(std::move(batch_))\n    , reader(std::move(reader))\n    {\n    }\n\n    pod5::ReadTableRecordBatch const batch;\n    std::shared_ptr<pod5::FileReader const> reader;\n};\n\nnamespace {\n//---------------------------------------------------------------------------------------------------------------------\nthread_local pod5_error_t g_pod5_error_no;\nthread_local std::string g_pod5_error_string;\n\nvoid pod5_set_error(arrow::Status status)\n{\n    g_pod5_error_no = (pod5_error_t)status.code();\n    g_pod5_error_string = status.ToString();\n}\n\nvoid pod5_reset_error()\n{\n    g_pod5_error_no = pod5_error_t::POD5_OK;\n    g_pod5_error_string.clear();\n}\n\n#define POD5_C_RETURN_NOT_OK(result)    \\\n    do {                                \\\n        ::arrow::Status __s = (result); \\\n        if (!__s.ok()) {                \\\n            pod5_set_error(__s);        \\\n            return g_pod5_error_no;     \\\n        }                               \\\n    } while (0)\n\n#define POD5_C_ASSIGN_OR_RAISE_IMPL(result_name, lhs, rexpr) \\\n    auto && result_name = (rexpr);                           \\\n    if (!(result_name).ok()) {                               \\\n        pod5_set_error((result_name).status());              \\\n        return g_pod5_error_no;                              \\\n    }                                                        \\\n    lhs = std::move(result_name).ValueUnsafe();\n\n#define POD5_C_ASSIGN_OR_RAISE(lhs, rexpr) \\\n    POD5_C_ASSIGN_OR_RAISE_IMPL(           \\\n        ARROW_ASSIGN_OR_RAISE_NAME(_error_or_value, __COUNTER__), lhs, rexpr);\n\n//---------------------------------------------------------------------------------------------------------------------\nbool check_string_not_empty(char const * str)\n{\n    if (!str) {\n        pod5_set_error(arrow::Status::Invalid(\"null string passed to C API\"));\n        return false;\n    }\n\n    if (strlen(str) == 0) {\n        pod5_set_error(arrow::Status::Invalid(\"empty string passed to C API\"));\n        return false;\n    }\n\n    return true;\n}\n\nbool check_not_null(void const * ptr)\n{\n    if (!ptr) {\n        pod5_set_error(arrow::Status::Invalid(\"null passed to C API\"));\n        return false;\n    }\n    return true;\n}\n\nbool check_file_not_null(void const * file)\n{\n    if (!file) {\n        pod5_set_error(arrow::Status::Invalid(\"null file passed to C API\"));\n        return false;\n    }\n    return true;\n}\n\nbool check_output_pointer_not_null(void const * output)\n{\n    if (!output) {\n        pod5_set_error(arrow::Status::Invalid(\"null output parameter passed to C API\"));\n        return false;\n    }\n    return true;\n}\n\n//---------------------------------------------------------------------------------------------------------------------\npod5::FileWriterOptions make_internal_writer_options(Pod5WriterOptions const * options)\n{\n    pod5::FileWriterOptions internal_options;\n    if (options) {\n        if (options->max_signal_chunk_size != 0) {\n            internal_options.set_max_signal_chunk_size(options->max_signal_chunk_size);\n        }\n\n        if (options->signal_compression_type == UNCOMPRESSED_SIGNAL) {\n            internal_options.set_signal_type(pod5::SignalType::UncompressedSignal);\n        }\n\n        if (options->signal_table_batch_size != 0) {\n            internal_options.set_signal_table_batch_size(options->signal_table_batch_size);\n        }\n        if (options->read_table_batch_size != 0) {\n            internal_options.set_read_table_batch_size(options->read_table_batch_size);\n        }\n    }\n    return internal_options;\n}\n\npod5::FileReaderOptions make_internal_reader_options(Pod5ReaderOptions const & options)\n{\n    pod5::FileReaderOptions internal_options;\n    internal_options.set_force_disable_file_mapping(options.force_disable_file_mapping);\n    return internal_options;\n}\n\n}  // namespace\n\nextern \"C\" {\n\n//---------------------------------------------------------------------------------------------------------------------\npod5_error_t pod5_init()\n{\n    pod5_reset_error();\n    POD5_C_RETURN_NOT_OK(pod5::register_extension_types());\n    return POD5_OK;\n}\n\npod5_error_t pod5_terminate()\n{\n    pod5_reset_error();\n    POD5_C_RETURN_NOT_OK(pod5::unregister_extension_types());\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_error_no() { return g_pod5_error_no; }\n\nchar const * pod5_get_error_string() { return g_pod5_error_string.c_str(); }\n\n//---------------------------------------------------------------------------------------------------------------------\nPod5FileReader * pod5_open_file(char const * filename)\n{\n    Pod5ReaderOptions_t options{};\n    return pod5_open_file_options(filename, &options);\n}\n\nPod5FileReader * pod5_open_file_options(char const * filename, Pod5ReaderOptions_t const * options)\n{\n    pod5_reset_error();\n\n    if (!check_string_not_empty(filename) || !check_not_null(options)) {\n        return nullptr;\n    }\n\n    auto internal_reader = pod5::open_file_reader(filename, make_internal_reader_options(*options));\n    if (!internal_reader.ok()) {\n        pod5_set_error(internal_reader.status());\n        return nullptr;\n    }\n\n    auto reader = std::make_unique<Pod5FileReader>(std::move(*internal_reader));\n    return reader.release();\n}\n\npod5_error_t pod5_close_and_free_reader(Pod5FileReader * file)\n{\n    pod5_reset_error();\n\n    std::unique_ptr<Pod5FileReader> ptr{file};\n    ptr.reset();\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_file_info(Pod5FileReader_t const * reader, FileInfo * file_info)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(reader) || !check_output_pointer_not_null(file_info)) {\n        return g_pod5_error_no;\n    }\n\n    auto const metadata = reader->reader->schema_metadata();\n    metadata.file_identifier.to_c_array(file_info->file_identifier);\n\n    file_info->version.major = metadata.writing_pod5_version.major_version();\n    file_info->version.minor = metadata.writing_pod5_version.minor_version();\n    file_info->version.revision = metadata.writing_pod5_version.revision_version();\n\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_file_read_table_location(\n    Pod5FileReader_t const * reader,\n    EmbeddedFileData_t * file_data)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(reader) || !check_output_pointer_not_null(file_data)) {\n        return g_pod5_error_no;\n    }\n    auto const & read_table_location = reader->reader->read_table_location();\n\n    file_data->file_name = read_table_location.file_path.c_str();\n    file_data->offset = read_table_location.offset;\n    file_data->length = read_table_location.size;\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_file_signal_table_location(\n    Pod5FileReader_t const * reader,\n    EmbeddedFileData_t * file_data)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(reader) || !check_output_pointer_not_null(file_data)) {\n        return g_pod5_error_no;\n    }\n    auto const & signal_table_location = reader->reader->signal_table_location();\n\n    file_data->file_name = signal_table_location.file_path.c_str();\n    file_data->offset = signal_table_location.offset;\n    file_data->length = signal_table_location.size;\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_file_run_info_table_location(\n    Pod5FileReader_t const * reader,\n    EmbeddedFileData_t * file_data)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(reader) || !check_output_pointer_not_null(file_data)) {\n        return g_pod5_error_no;\n    }\n    auto const & run_info_table_location = reader->reader->run_info_table_location();\n\n    file_data->file_name = run_info_table_location.file_path.c_str();\n    file_data->offset = run_info_table_location.offset;\n    file_data->length = run_info_table_location.size;\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_read_count(Pod5FileReader_t const * reader, size_t * count)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(reader) || !check_output_pointer_not_null(count)) {\n        return g_pod5_error_no;\n    }\n\n    POD5_C_ASSIGN_OR_RAISE(*count, reader->reader->read_count());\n\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_read_ids(Pod5FileReader_t const * reader, size_t count, read_id_t * read_ids)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(reader) || !check_output_pointer_not_null(read_ids)) {\n        return g_pod5_error_no;\n    }\n\n    POD5_C_ASSIGN_OR_RAISE(auto read_count, reader->reader->read_count());\n    if (count < read_count) {\n        pod5_set_error(arrow::Status::Invalid(\"array to short to receive read ids\"));\n        return g_pod5_error_no;\n    }\n\n    std::size_t count_so_far = 0;\n    for (std::size_t i = 0; i < reader->reader->num_read_record_batches(); ++i) {\n        POD5_C_ASSIGN_OR_RAISE(auto const batch, reader->reader->read_read_record_batch(i));\n\n        auto const read_id_column = batch.read_id_column();\n        auto raw_data = reinterpret_cast<uint8_t const *>(read_id_column->raw_values());\n        std::copy(\n            raw_data,\n            raw_data + (read_id_column->length() * sizeof(read_id_t)),\n            reinterpret_cast<uint8_t *>(read_ids + count_so_far));\n        count_so_far += read_id_column->length();\n    }\n\n    return POD5_OK;\n}\n\npod5_error_t pod5_plan_traversal(\n    Pod5FileReader_t const * reader,\n    uint8_t const * read_id_array,\n    size_t read_id_count,\n    uint32_t * batch_counts,\n    uint32_t * batch_rows,\n    size_t * find_success_count_out)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(reader) || !check_not_null(read_id_array)\n        || !check_output_pointer_not_null(batch_counts)\n        || !check_output_pointer_not_null(batch_rows)\n        || !check_output_pointer_not_null(find_success_count_out))\n    {\n        return g_pod5_error_no;\n    }\n\n    auto search_input = pod5::ReadIdSearchInput(\n        gsl::make_span(reinterpret_cast<pod5::Uuid const *>(read_id_array), read_id_count));\n\n    POD5_C_ASSIGN_OR_RAISE(\n        auto find_success_count,\n        reader->reader->search_for_read_ids(\n            search_input,\n            gsl::make_span(batch_counts, reader->reader->num_read_record_batches()),\n            gsl::make_span(batch_rows, read_id_count)));\n\n    // TODO: on MAJOR_VERSION bump drop this out param and do the check internally.\n    *find_success_count_out = find_success_count;\n\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_read_batch_count(size_t * count, Pod5FileReader const * reader)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(reader) || !check_output_pointer_not_null(count)) {\n        return g_pod5_error_no;\n    }\n\n    *count = reader->reader->num_read_record_batches();\n    return POD5_OK;\n}\n\npod5_error_t\npod5_get_read_batch(Pod5ReadRecordBatch ** batch, Pod5FileReader const * reader, size_t index)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(reader) || !check_output_pointer_not_null(batch)) {\n        return g_pod5_error_no;\n    }\n\n    POD5_C_ASSIGN_OR_RAISE(auto internal_batch, reader->reader->read_read_record_batch(index));\n\n    auto wrapped_batch =\n        std::make_unique<Pod5ReadRecordBatch>(std::move(internal_batch), reader->reader);\n\n    *batch = wrapped_batch.release();\n    return POD5_OK;\n}\n\npod5_error_t pod5_free_read_batch(Pod5ReadRecordBatch * batch)\n{\n    pod5_reset_error();\n\n    std::unique_ptr<Pod5ReadRecordBatch> ptr{batch};\n    ptr.reset();\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_read_batch_row_count(size_t * count, Pod5ReadRecordBatch const * batch)\n{\n    pod5_reset_error();\n\n    if (!check_not_null(batch) || !check_output_pointer_not_null(count)) {\n        return g_pod5_error_no;\n    }\n\n    *count = batch->batch.num_rows();\n    return POD5_OK;\n}\n\nstatic pod5_error_t check_row_index_and_set_error(size_t row, int64_t batch_size)\n{\n    if (row > static_cast<size_t>(std::numeric_limits<int64_t>::max())\n        || static_cast<int64_t>(row) >= batch_size)\n    {\n        pod5_set_error(\n            arrow::Status::IndexError(\n                \"Invalid index into batch. Index \", row, \" with batch size \", batch_size));\n        return g_pod5_error_no;\n    }\n\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_read_batch_row_info_data(\n    Pod5ReadRecordBatch_t const * batch,\n    size_t row,\n    uint16_t struct_version,\n    void * row_data,\n    uint16_t * read_table_version)\n{\n    pod5_reset_error();\n\n    if (!check_not_null(batch) || !check_output_pointer_not_null(row_data)\n        || !check_output_pointer_not_null(read_table_version))\n    {\n        return g_pod5_error_no;\n    }\n\n    static_assert(\n        READ_BATCH_ROW_INFO_VERSION == READ_BATCH_ROW_INFO_VERSION_4,\n        \"New versions must be explicitly loaded\");\n\n    auto load_common_v3_v4_fields = [](pod5::ReadTableRecordColumns const & cols,\n                                       std::size_t row,\n                                       auto * typed_row_data) {\n        // Inform the caller of the version of the input table.\n\n        if (check_row_index_and_set_error(row, cols.read_id->length()) != POD5_OK) {\n            return g_pod5_error_no;\n        }\n\n        auto read_id_val = cols.read_id->Value(row);\n        read_id_val.to_c_array(typed_row_data->read_id);\n\n        typed_row_data->read_number = cols.read_number->Value(row);\n        typed_row_data->start_sample = cols.start_sample->Value(row);\n        typed_row_data->median_before = cols.median_before->Value(row);\n        typed_row_data->channel = cols.channel->Value(row);\n        typed_row_data->well = cols.well->Value(row);\n        auto const & pore_type_col = cols.pore_type->indices();\n        typed_row_data->pore_type =\n            static_cast<arrow::Int16Array const &>(*pore_type_col).Value(row);\n        typed_row_data->calibration_offset = cols.calibration_offset->Value(row);\n        typed_row_data->calibration_scale = cols.calibration_scale->Value(row);\n        auto const & end_reason_col = cols.end_reason->indices();\n        typed_row_data->end_reason =\n            static_cast<arrow::Int16Array const &>(*end_reason_col).Value(row);\n        typed_row_data->end_reason_forced = cols.end_reason_forced->Value(row);\n        auto const & run_info_col = cols.run_info->indices();\n        typed_row_data->run_info = static_cast<arrow::Int16Array const &>(*run_info_col).Value(row);\n        typed_row_data->num_minknow_events = cols.num_minknow_events->Value(row);\n        typed_row_data->tracked_scaling_scale = cols.tracked_scaling_scale->Value(row);\n        typed_row_data->tracked_scaling_shift = cols.tracked_scaling_shift->Value(row);\n        typed_row_data->predicted_scaling_scale = cols.predicted_scaling_scale->Value(row);\n        typed_row_data->predicted_scaling_shift = cols.predicted_scaling_shift->Value(row);\n        typed_row_data->num_reads_since_mux_change = cols.num_reads_since_mux_change->Value(row);\n        typed_row_data->time_since_mux_change = cols.time_since_mux_change->Value(row);\n\n        typed_row_data->signal_row_count = cols.signal->value_length(row);\n        typed_row_data->num_samples = cols.num_samples->Value(row);\n        return POD5_OK;\n    };\n\n    if (struct_version == READ_BATCH_ROW_INFO_VERSION_3) {\n        auto typed_row_data = static_cast<ReadBatchRowInfoV3 *>(row_data);\n\n        POD5_C_ASSIGN_OR_RAISE(auto cols, batch->batch.columns());\n        *read_table_version = cols.table_version.as_int();\n\n        auto result = load_common_v3_v4_fields(cols, row, typed_row_data);\n        if (result != POD5_OK) {\n            return result;\n        }\n    } else if (struct_version == READ_BATCH_ROW_INFO_VERSION_4) {\n        auto typed_row_data = static_cast<ReadBatchRowInfoV4 *>(row_data);\n\n        POD5_C_ASSIGN_OR_RAISE(auto cols, batch->batch.columns());\n        *read_table_version = cols.table_version.as_int();\n\n        auto result = load_common_v3_v4_fields(cols, row, typed_row_data);\n        if (result != POD5_OK) {\n            return result;\n        }\n\n        // This is the only difference between v3 and v4.\n        typed_row_data->open_pore_level = cols.open_pore_level->Value(row);\n    } else {\n        pod5_set_error(\n            arrow::Status::Invalid(\"Invalid struct version '\", struct_version, \"' passed\"));\n        return g_pod5_error_no;\n    }\n\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_signal_row_indices(\n    Pod5ReadRecordBatch const * batch,\n    size_t row,\n    int64_t signal_row_indices_count,\n    uint64_t * signal_row_indices)\n{\n    pod5_reset_error();\n\n    if (!check_not_null(batch) || !check_output_pointer_not_null(signal_row_indices)) {\n        return g_pod5_error_no;\n    }\n\n    auto const signal_col = batch->batch.signal_column();\n    if (check_row_index_and_set_error(row, signal_col->length()) != POD5_OK) {\n        return g_pod5_error_no;\n    }\n\n    auto const row_data =\n        std::static_pointer_cast<arrow::UInt64Array>(signal_col->value_slice(row));\n\n    if (signal_row_indices_count != row_data->length()) {\n        pod5_set_error(\n            pod5::Status::Invalid(\n                \"Incorrect number of signal indices, expected \",\n                row_data->length(),\n                \" received \",\n                signal_row_indices_count));\n        return g_pod5_error_no;\n    }\n\n    for (std::int64_t i = 0; i < signal_row_indices_count; ++i) {\n        signal_row_indices[i] = row_data->Value(i);\n    }\n\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_calibration_extra_info(\n    Pod5ReadRecordBatch_t const * batch,\n    size_t row,\n    CalibrationExtraData_t * calibration_extra_data)\n{\n    pod5_reset_error();\n\n    if (!check_not_null(batch) || !check_output_pointer_not_null(calibration_extra_data)) {\n        return g_pod5_error_no;\n    }\n\n    POD5_C_ASSIGN_OR_RAISE(auto cols, batch->batch.columns());\n\n    if (check_row_index_and_set_error(row, cols.calibration_scale->length()) != POD5_OK) {\n        return g_pod5_error_no;\n    }\n\n    auto scale = cols.calibration_scale->Value(row);\n    auto const & run_info_indices = cols.run_info->indices();\n    auto const run_info_dict_index =\n        static_cast<arrow::Int16Array const &>(*run_info_indices).Value(row);\n\n    POD5_C_ASSIGN_OR_RAISE(\n        auto const acquisition_id, batch->batch.get_run_info(run_info_dict_index));\n    POD5_C_ASSIGN_OR_RAISE(auto run_info_data, batch->reader->find_run_info(acquisition_id));\n\n    calibration_extra_data->digitisation = run_info_data->adc_max - run_info_data->adc_min + 1;\n    calibration_extra_data->range = scale * calibration_extra_data->digitisation;\n\n    return POD5_OK;\n}\n\nnamespace {\n\nstruct RunInfoDataCHelper : public RunInfoDictData {\n    struct InternalMapHelper {\n        std::vector<char const *> keys;\n        std::vector<char const *> values;\n    };\n\n    RunInfoDataCHelper(std::shared_ptr<pod5::RunInfoData const> && internal_data_)\n    : internal_data(std::move(internal_data_))\n    {\n        acquisition_id = internal_data->acquisition_id.c_str();\n        acquisition_start_time_ms = internal_data->acquisition_start_time;\n        adc_max = internal_data->adc_max;\n        adc_min = internal_data->adc_min;\n        context_tags = map_to_c(internal_data->context_tags, context_tags_helper);\n        experiment_name = internal_data->experiment_name.c_str();\n        flow_cell_id = internal_data->flow_cell_id.c_str();\n        flow_cell_product_code = internal_data->flow_cell_product_code.c_str();\n        protocol_name = internal_data->protocol_name.c_str();\n        protocol_run_id = internal_data->protocol_run_id.c_str();\n        protocol_start_time_ms = internal_data->protocol_start_time;\n        sample_id = internal_data->sample_id.c_str();\n        sample_rate = internal_data->sample_rate;\n        sequencing_kit = internal_data->sequencing_kit.c_str();\n        sequencer_position = internal_data->sequencer_position.c_str();\n        sequencer_position_type = internal_data->sequencer_position_type.c_str();\n        software = internal_data->software.c_str();\n        system_name = internal_data->system_name.c_str();\n        system_type = internal_data->system_type.c_str();\n        tracking_id = map_to_c(internal_data->tracking_id, tracking_id_helper);\n    }\n\n    KeyValueData map_to_c(pod5::RunInfoData::MapType const & map, InternalMapHelper & helper)\n    {\n        helper.keys.reserve(map.size());\n        helper.values.reserve(map.size());\n        for (auto const & item : map) {\n            helper.keys.push_back(item.first.c_str());\n            helper.values.push_back(item.second.c_str());\n        }\n\n        KeyValueData result;\n        result.size = helper.keys.size();\n        result.keys = helper.keys.data();\n        result.values = helper.values.data();\n        return result;\n    }\n\n    std::shared_ptr<pod5::RunInfoData const> internal_data;\n    InternalMapHelper context_tags_helper;\n    InternalMapHelper tracking_id_helper;\n};\n\n}  // namespace\n\npod5_error_t pod5_get_run_info(\n    Pod5ReadRecordBatch const * batch,\n    int16_t run_info,\n    RunInfoDictData ** run_info_data)\n{\n    pod5_reset_error();\n\n    if (!check_not_null(batch) || !check_output_pointer_not_null(run_info_data)) {\n        return g_pod5_error_no;\n    }\n\n    POD5_C_ASSIGN_OR_RAISE(auto const acquisition_id, batch->batch.get_run_info(run_info));\n    POD5_C_ASSIGN_OR_RAISE(auto internal_data, batch->reader->find_run_info(acquisition_id));\n\n    auto data = std::make_unique<RunInfoDataCHelper>(std::move(internal_data));\n    *run_info_data = data.release();\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_file_run_info(\n    Pod5FileReader_t const * file,\n    run_info_index_t run_info_index,\n    RunInfoDictData_t ** run_info_data)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(file) || !check_output_pointer_not_null(run_info_data)) {\n        return g_pod5_error_no;\n    }\n\n    POD5_C_ASSIGN_OR_RAISE(auto internal_data, file->reader->get_run_info(run_info_index));\n\n    auto data = std::make_unique<RunInfoDataCHelper>(std::move(internal_data));\n    *run_info_data = data.release();\n    return POD5_OK;\n}\n\npod5_error_t pod5_free_run_info(RunInfoDictData_t * run_info_data)\n{\n    pod5_reset_error();\n\n    std::unique_ptr<RunInfoDataCHelper> helper(static_cast<RunInfoDataCHelper *>(run_info_data));\n    helper.reset();\n\n    return POD5_OK;\n}\n\npod5_error_t pod5_release_run_info(RunInfoDictData * run_info_data)\n{\n    return pod5_free_run_info(run_info_data);\n}\n\npod5_error_t pod5_get_file_run_info_count(\n    Pod5FileReader_t const * file,\n    run_info_index_t * run_info_count)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(file) || !check_output_pointer_not_null(run_info_count)) {\n        return g_pod5_error_no;\n    }\n\n    POD5_C_ASSIGN_OR_RAISE(*run_info_count, file->reader->get_run_info_count());\n\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_end_reason(\n    Pod5ReadRecordBatch_t const * batch,\n    int16_t end_reason,\n    pod5_end_reason * end_reason_value,\n    char * end_reason_string_value,\n    size_t * end_reason_string_value_size)\n{\n    pod5_reset_error();\n\n    if (!check_not_null(batch) || !check_output_pointer_not_null(end_reason_value)\n        || !check_output_pointer_not_null(end_reason_string_value)\n        || !check_output_pointer_not_null(end_reason_string_value_size))\n    {\n        return g_pod5_error_no;\n    }\n\n    POD5_C_ASSIGN_OR_RAISE(auto const end_reason_val, batch->batch.get_end_reason(end_reason));\n    auto const input_buffer_len = *end_reason_string_value_size;\n    *end_reason_string_value_size = end_reason_val.second.size() + 1;\n    if (end_reason_val.second.size() >= input_buffer_len) {\n        return POD5_ERROR_STRING_NOT_LONG_ENOUGH;\n    }\n\n    *end_reason_value = POD5_END_REASON_UNKNOWN;\n    switch (end_reason_val.first) {\n    case pod5::ReadEndReason::mux_change:\n        *end_reason_value = POD5_END_REASON_MUX_CHANGE;\n        break;\n    case pod5::ReadEndReason::unblock_mux_change:\n        *end_reason_value = POD5_END_REASON_UNBLOCK_MUX_CHANGE;\n        break;\n    case pod5::ReadEndReason::data_service_unblock_mux_change:\n        *end_reason_value = POD5_END_REASON_DATA_SERVICE_UNBLOCK_MUX_CHANGE;\n        break;\n    case pod5::ReadEndReason::signal_positive:\n        *end_reason_value = POD5_END_REASON_SIGNAL_POSITIVE;\n        break;\n    case pod5::ReadEndReason::signal_negative:\n        *end_reason_value = POD5_END_REASON_SIGNAL_NEGATIVE;\n        break;\n    case pod5::ReadEndReason::api_request:\n        *end_reason_value = POD5_END_REASON_API_REQUEST;\n        break;\n    case pod5::ReadEndReason::device_data_error:\n        *end_reason_value = POD5_END_REASON_DEVICE_DATA_ERROR;\n        break;\n    case pod5::ReadEndReason::analysis_config_change:\n        *end_reason_value = POD5_END_REASON_ANALYSIS_CONFIG_CHANGE;\n        break;\n    case pod5::ReadEndReason::paused:\n        *end_reason_value = POD5_END_REASON_PAUSED;\n        break;\n    case pod5::ReadEndReason::unknown:\n        *end_reason_value = POD5_END_REASON_UNKNOWN;\n        break;\n    }\n\n    std::copy(end_reason_val.second.begin(), end_reason_val.second.end(), end_reason_string_value);\n    end_reason_string_value[*end_reason_string_value_size] = '\\0';\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_pore_type(\n    Pod5ReadRecordBatch_t const * batch,\n    int16_t pore_type,\n    char * pore_type_string_value,\n    size_t * pore_type_string_value_size)\n{\n    pod5_reset_error();\n\n    if (!check_not_null(batch) || !check_output_pointer_not_null(pore_type_string_value)\n        || !check_output_pointer_not_null(pore_type_string_value_size))\n    {\n        return g_pod5_error_no;\n    }\n\n    POD5_C_ASSIGN_OR_RAISE(auto const pore_type_str, batch->batch.get_pore_type(pore_type));\n    auto const input_buffer_len = *pore_type_string_value_size;\n    *pore_type_string_value_size = pore_type_str.size() + 1;\n    if (pore_type_str.size() >= input_buffer_len) {\n        return POD5_ERROR_STRING_NOT_LONG_ENOUGH;\n    }\n\n    std::copy(pore_type_str.begin(), pore_type_str.end(), pore_type_string_value);\n    pore_type_string_value[*pore_type_string_value_size] = '\\0';\n    return POD5_OK;\n}\n\nnamespace {\n\nclass SignalRowInfoCHelper : public SignalRowInfo {\npublic:\n    SignalRowInfoCHelper(pod5::SignalTableRecordBatch && b) : batch(std::move(b)) {}\n\n    pod5::SignalTableRecordBatch const batch;\n};\n\n}  // namespace\n\npod5_error_t pod5_get_signal_row_info(\n    Pod5FileReader const * reader,\n    size_t signal_rows_count,\n    uint64_t const * signal_rows,\n    SignalRowInfo ** signal_row_info)\n{\n    pod5_reset_error();\n\n    // Check for a valid reader.\n    if (!check_file_not_null(reader)) {\n        return g_pod5_error_no;\n    }\n\n    // Check for valid inputs.\n    if (signal_rows_count == 0) {\n        // Nothing to do.\n        return POD5_OK;\n    } else if (!check_not_null(signal_rows) || !check_output_pointer_not_null(signal_row_info)) {\n        return g_pod5_error_no;\n    }\n\n    // Sort all rows first, in order to make searching faster.\n    std::vector<std::uint64_t> signal_rows_sorted{signal_rows, signal_rows + signal_rows_count};\n    std::sort(signal_rows_sorted.begin(), signal_rows_sorted.end());\n\n    // Store allocations to a temporary buffer so that we don't leak them on failure.\n    std::vector<std::unique_ptr<SignalRowInfoCHelper>> row_infos(signal_rows_count);\n\n    // Then loop all rows, forward.\n    for (std::size_t completed_rows = 0; completed_rows < signal_rows_sorted.size();\n         completed_rows++)\n    {\n        auto const start_row = signal_rows_sorted[completed_rows];\n\n        std::size_t batch_row = 0;\n        POD5_C_ASSIGN_OR_RAISE(\n            std::size_t row_batch,\n            (reader->reader->signal_batch_for_row_id(start_row, &batch_row)));\n        POD5_C_ASSIGN_OR_RAISE(auto batch, reader->reader->read_signal_record_batch(row_batch));\n\n        auto output = std::make_unique<SignalRowInfoCHelper>(std::move(batch));\n\n        output->batch_index = start_row;\n        output->batch_row_index = batch_row;\n\n        auto samples = output->batch.samples_column();\n        output->stored_sample_count = samples->Value(batch_row);\n        POD5_C_ASSIGN_OR_RAISE(\n            output->stored_byte_count, output->batch.samples_byte_count(batch_row));\n\n        row_infos[completed_rows] = std::move(output);\n    }\n\n    // Pass ownership of the info back to the caller.\n    for (std::size_t row_idx = 0; row_idx < signal_rows_count; row_idx++) {\n        signal_row_info[row_idx] = row_infos[row_idx].release();\n    }\n    return POD5_OK;\n}\n\npod5_error_t pod5_free_signal_row_info(size_t signal_rows_count, SignalRowInfo_t ** signal_row_info)\n{\n    pod5_reset_error();\n\n    if (signal_rows_count > 0 && !check_not_null(signal_row_info)) {\n        return g_pod5_error_no;\n    }\n\n    for (std::size_t i = 0; i < signal_rows_count; ++i) {\n        std::unique_ptr<SignalRowInfoCHelper> helper(\n            static_cast<SignalRowInfoCHelper *>(signal_row_info[i]));\n        helper.reset();\n    }\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_signal(\n    Pod5FileReader const * reader,\n    SignalRowInfo_t const * row_info,\n    size_t sample_count,\n    int16_t * sample_data)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(reader) || !check_not_null(row_info)\n        || !check_output_pointer_not_null(sample_data))\n    {\n        return g_pod5_error_no;\n    }\n\n    auto * row_info_data = static_cast<SignalRowInfoCHelper const *>(row_info);\n\n    POD5_C_RETURN_NOT_OK(row_info_data->batch.extract_signal_row(\n        row_info->batch_row_index, gsl::make_span(sample_data, sample_count)));\n\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_read_complete_sample_count(\n    Pod5FileReader_t const * reader,\n    Pod5ReadRecordBatch_t const * batch,\n    size_t batch_row,\n    size_t * sample_count)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(reader) || !check_not_null(batch)\n        || !check_output_pointer_not_null(sample_count))\n    {\n        return g_pod5_error_no;\n    }\n\n    POD5_C_ASSIGN_OR_RAISE(auto const & signal_rows, batch->batch.get_signal_rows(batch_row));\n\n    POD5_C_ASSIGN_OR_RAISE(\n        *sample_count,\n        reader->reader->extract_sample_count(\n            gsl::make_span(signal_rows->raw_values(), signal_rows->length())));\n    return POD5_OK;\n}\n\npod5_error_t pod5_get_read_complete_signal(\n    Pod5FileReader_t const * reader,\n    Pod5ReadRecordBatch_t const * batch,\n    size_t batch_row,\n    size_t sample_count,\n    int16_t * signal)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(reader) || !check_not_null(batch)\n        || !check_output_pointer_not_null(signal))\n    {\n        return g_pod5_error_no;\n    }\n\n    POD5_C_ASSIGN_OR_RAISE(auto const & signal_rows, batch->batch.get_signal_rows(batch_row));\n\n    POD5_C_RETURN_NOT_OK(reader->reader->extract_samples(\n        gsl::make_span(signal_rows->raw_values(), signal_rows->length()),\n        gsl::make_span(signal, sample_count)));\n    return POD5_OK;\n}\n\n//---------------------------------------------------------------------------------------------------------------------\nPod5FileWriter *\npod5_create_file(char const * filename, char const * writer_name, Pod5WriterOptions const * options)\n{\n    pod5_reset_error();\n\n    if (!check_string_not_empty(filename) || !check_string_not_empty(writer_name)) {\n        return nullptr;\n    }\n\n    auto internal_writer =\n        pod5::create_file_writer(filename, writer_name, make_internal_writer_options(options));\n    if (!internal_writer.ok()) {\n        pod5_set_error(internal_writer.status());\n        return nullptr;\n    }\n\n    auto writer = std::make_unique<Pod5FileWriter>(std::move(*internal_writer));\n    return writer.release();\n}\n\npod5_error_t pod5_close_and_free_writer(Pod5FileWriter * file)\n{\n    pod5_reset_error();\n\n    std::unique_ptr<Pod5FileWriter> ptr{file};\n    if (ptr) {\n        POD5_C_RETURN_NOT_OK(ptr->writer->close());\n    }\n\n    ptr.reset();\n    return POD5_OK;\n}\n\npod5_error_t pod5_add_pore(int16_t * pore_index, Pod5FileWriter * file, char const * pore_type)\n{\n    pod5_reset_error();\n\n    if (!check_string_not_empty(pore_type) || !check_file_not_null(file)\n        || !check_output_pointer_not_null(pore_index))\n    {\n        return g_pod5_error_no;\n    }\n\n    POD5_C_ASSIGN_OR_RAISE(*pore_index, file->writer->add_pore_type(pore_type));\n    return POD5_OK;\n}\n\npod5_error_t pod5_add_run_info(\n    int16_t * run_info_index,\n    Pod5FileWriter * file,\n    char const * acquisition_id,\n    int64_t acquisition_start_time_ms,\n    int16_t adc_max,\n    int16_t adc_min,\n    size_t context_tags_count,\n    char const ** context_tags_keys,\n    char const ** context_tags_values,\n    char const * experiment_name,\n    char const * flow_cell_id,\n    char const * flow_cell_product_code,\n    char const * protocol_name,\n    char const * protocol_run_id,\n    int64_t protocol_start_time_ms,\n    char const * sample_id,\n    uint16_t sample_rate,\n    char const * sequencing_kit,\n    char const * sequencer_position,\n    char const * sequencer_position_type,\n    char const * software,\n    char const * system_name,\n    char const * system_type,\n    size_t tracking_id_count,\n    char const ** tracking_id_keys,\n    char const ** tracking_id_values)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(file) || !check_not_null(run_info_index)\n        || !check_string_not_empty(acquisition_id) || !check_string_not_empty(experiment_name)\n        || !check_string_not_empty(flow_cell_id) || !check_string_not_empty(flow_cell_product_code)\n        || !check_string_not_empty(protocol_name) || !check_string_not_empty(protocol_run_id)\n        || !check_string_not_empty(sample_id) || !check_string_not_empty(sequencing_kit)\n        || !check_string_not_empty(sequencer_position)\n        || !check_string_not_empty(sequencer_position_type) || !check_string_not_empty(software)\n        || !check_string_not_empty(system_name) || !check_string_not_empty(system_type))\n    {\n        return g_pod5_error_no;\n    }\n\n    auto const parse_map =\n        [](std::size_t tracking_id_count,\n           char const ** tracking_id_keys,\n           char const ** tracking_id_values) -> pod5::Result<pod5::RunInfoData::MapType> {\n        if (!check_not_null(tracking_id_keys) || !check_not_null(tracking_id_values)) {\n            return arrow::Status::Invalid(g_pod5_error_string);\n        }\n\n        pod5::RunInfoData::MapType result;\n        for (std::size_t i = 0; i < tracking_id_count; ++i) {\n            auto key = tracking_id_keys[i];\n            auto value = tracking_id_values[i];\n            if (!check_string_not_empty(key) || !check_string_not_empty(value)) {\n                return arrow::Status::Invalid(g_pod5_error_string);\n            }\n\n            result.emplace_back(key, value);\n        }\n        return result;\n    };\n\n    POD5_C_ASSIGN_OR_RAISE(\n        auto const context_tags,\n        parse_map(context_tags_count, context_tags_keys, context_tags_values));\n    POD5_C_ASSIGN_OR_RAISE(\n        auto const tracking_id, parse_map(tracking_id_count, tracking_id_keys, tracking_id_values));\n\n    POD5_C_ASSIGN_OR_RAISE(\n        *run_info_index,\n        file->writer->add_run_info(\n            pod5::RunInfoData(\n                acquisition_id,\n                acquisition_start_time_ms,\n                adc_max,\n                adc_min,\n                context_tags,\n                experiment_name,\n                flow_cell_id,\n                flow_cell_product_code,\n                protocol_name,\n                protocol_run_id,\n                protocol_start_time_ms,\n                sample_id,\n                sample_rate,\n                sequencing_kit,\n                sequencer_position,\n                sequencer_position_type,\n                software,\n                system_name,\n                system_type,\n                tracking_id)));\n\n    return POD5_OK;\n}\n\nstatic bool check_read_data_struct(std::uint16_t struct_version, void const * row_data)\n{\n    static_assert(\n        READ_BATCH_ROW_INFO_VERSION == READ_BATCH_ROW_INFO_VERSION_4,\n        \"New versions must be explicitly loaded\");\n\n    if (!check_not_null(row_data)) {\n        return false;\n    }\n\n    if (struct_version < READ_BATCH_ROW_INFO_VERSION_3) {\n        pod5_set_error(arrow::Status::Invalid(\"Unable to write V1 + V2 reads, update to V3 API.\"));\n        return false;\n    }\n\n    auto check_common_v3_v4_fields = [](auto typed_row_data) -> bool {\n        return check_not_null(typed_row_data->read_id)\n               && check_not_null(typed_row_data->read_number)\n               && check_not_null(typed_row_data->start_sample)\n               && check_not_null(typed_row_data->median_before)\n               && check_not_null(typed_row_data->channel) && check_not_null(typed_row_data->well)\n               && check_not_null(typed_row_data->pore_type)\n               && check_not_null(typed_row_data->calibration_offset)\n               && check_not_null(typed_row_data->calibration_scale)\n               && check_not_null(typed_row_data->end_reason)\n               && check_not_null(typed_row_data->end_reason_forced)\n               && check_not_null(typed_row_data->run_info_id)\n               && check_not_null(typed_row_data->num_minknow_events)\n               && check_not_null(typed_row_data->tracked_scaling_scale)\n               && check_not_null(typed_row_data->tracked_scaling_shift)\n               && check_not_null(typed_row_data->predicted_scaling_scale)\n               && check_not_null(typed_row_data->predicted_scaling_shift)\n               && check_not_null(typed_row_data->num_reads_since_mux_change)\n               && check_not_null(typed_row_data->time_since_mux_change);\n    };\n\n    if (struct_version == READ_BATCH_ROW_INFO_VERSION_3) {\n        auto const * typed_row_data = static_cast<ReadBatchRowInfoArrayV3 const *>(row_data);\n\n        if (!check_common_v3_v4_fields(typed_row_data)) {\n            return false;\n        }\n    }\n\n    if (struct_version == READ_BATCH_ROW_INFO_VERSION_4) {\n        auto const * typed_row_data = static_cast<ReadBatchRowInfoArrayV4 const *>(row_data);\n\n        if (!check_common_v3_v4_fields(typed_row_data)\n            || !check_not_null(typed_row_data->open_pore_level))\n        {\n            return false;\n        }\n    }\n\n    return true;\n}\n\nstatic bool load_struct_row_into_read_data(\n    std::unique_ptr<pod5::FileWriter> const & writer,\n    pod5::ReadData & read_data,\n    std::uint16_t struct_version,\n    void const * row_data,\n    std::uint32_t row_id)\n{\n    static_assert(\n        READ_BATCH_ROW_INFO_VERSION == READ_BATCH_ROW_INFO_VERSION_4,\n        \"New versions must be explicitly loaded\");\n\n    auto load_common_v3_v4_fields = [](std::unique_ptr<pod5::FileWriter> const & writer,\n                                       auto const * typed_row_data,\n                                       std::uint32_t row_id,\n                                       pod5::ReadData & read_data) {\n        pod5::Uuid read_id_uuid{typed_row_data->read_id[row_id]};\n\n        std::optional<pod5::ReadEndReason> end_reason_internal;\n        switch (typed_row_data->end_reason[row_id]) {\n        case POD5_END_REASON_UNKNOWN:\n            end_reason_internal = pod5::ReadEndReason::unknown;\n            break;\n        case POD5_END_REASON_MUX_CHANGE:\n            end_reason_internal = pod5::ReadEndReason::mux_change;\n            break;\n        case POD5_END_REASON_UNBLOCK_MUX_CHANGE:\n            end_reason_internal = pod5::ReadEndReason::unblock_mux_change;\n            break;\n        case POD5_END_REASON_DATA_SERVICE_UNBLOCK_MUX_CHANGE:\n            end_reason_internal = pod5::ReadEndReason::data_service_unblock_mux_change;\n            break;\n        case POD5_END_REASON_SIGNAL_POSITIVE:\n            end_reason_internal = pod5::ReadEndReason::signal_positive;\n            break;\n        case POD5_END_REASON_SIGNAL_NEGATIVE:\n            end_reason_internal = pod5::ReadEndReason::signal_negative;\n            break;\n        case POD5_END_REASON_API_REQUEST:\n            end_reason_internal = pod5::ReadEndReason::api_request;\n            break;\n        case POD5_END_REASON_DEVICE_DATA_ERROR:\n            end_reason_internal = pod5::ReadEndReason::device_data_error;\n            break;\n        case POD5_END_REASON_ANALYSIS_CONFIG_CHANGE:\n            end_reason_internal = pod5::ReadEndReason::analysis_config_change;\n            break;\n        case POD5_END_REASON_PAUSED:\n            end_reason_internal = pod5::ReadEndReason::paused;\n            break;\n        }\n        if (!end_reason_internal.has_value()) {\n            pod5_set_error(\n                arrow::Status::Invalid(\n                    \"out of range end reason \",\n                    typed_row_data->end_reason[row_id],\n                    \" passed to add read\"));\n            return false;\n        }\n\n        auto const end_reason_index = writer->lookup_end_reason(*end_reason_internal);\n        if (!end_reason_index.ok()) {\n            pod5_set_error(end_reason_index.status());\n            return false;\n        }\n\n        read_data = pod5::ReadData{\n            read_id_uuid,\n            typed_row_data->read_number[row_id],\n            typed_row_data->start_sample[row_id],\n            typed_row_data->channel[row_id],\n            typed_row_data->well[row_id],\n            typed_row_data->pore_type[row_id],\n            typed_row_data->calibration_offset[row_id],\n            typed_row_data->calibration_scale[row_id],\n            typed_row_data->median_before[row_id],\n            *end_reason_index,\n            typed_row_data->end_reason_forced[row_id] != 0,\n            typed_row_data->run_info_id[row_id],\n            typed_row_data->num_minknow_events[row_id],\n            typed_row_data->tracked_scaling_scale[row_id],\n            typed_row_data->tracked_scaling_shift[row_id],\n            typed_row_data->predicted_scaling_scale[row_id],\n            typed_row_data->predicted_scaling_shift[row_id],\n            typed_row_data->num_reads_since_mux_change[row_id],\n            typed_row_data->time_since_mux_change[row_id],\n            // open_pore_level is only present in v4.\n            std::numeric_limits<float>::quiet_NaN()};\n        return true;\n    };\n\n    // Version 0-2 are no longer supported for writing.\n    if (struct_version == READ_BATCH_ROW_INFO_VERSION_4) {\n        auto const * typed_row_data = static_cast<ReadBatchRowInfoArrayV4 const *>(row_data);\n\n        if (!load_common_v3_v4_fields(writer, typed_row_data, row_id, read_data)) {\n            return false;\n        }\n        read_data.open_pore_level = typed_row_data->open_pore_level[row_id];\n    } else if (struct_version == READ_BATCH_ROW_INFO_VERSION_3) {\n        auto const * typed_row_data = static_cast<ReadBatchRowInfoArrayV3 const *>(row_data);\n\n        if (!load_common_v3_v4_fields(writer, typed_row_data, row_id, read_data)) {\n            return false;\n        }\n    } else {\n        pod5_set_error(\n            arrow::Status::Invalid(\"Invalid writer struct version '\", struct_version, \"' passed\"));\n        return false;\n    }\n    return true;\n};\n\npod5_error_t pod5_add_reads_data(\n    Pod5FileWriter_t * file,\n    uint32_t read_count,\n    uint16_t struct_version,\n    void const * row_data,\n    int16_t const ** signal,\n    uint32_t const * signal_size)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(file)) {\n        return g_pod5_error_no;\n    }\n    if (read_count == 0) {\n        return POD5_OK;\n    }\n    if (!check_read_data_struct(struct_version, row_data) || !check_not_null(signal)\n        || !check_not_null(signal_size))\n    {\n        return g_pod5_error_no;\n    }\n    for (std::uint32_t read = 0; read < read_count; ++read) {\n        if (!check_not_null(signal[read])) {\n            return g_pod5_error_no;\n        }\n    }\n\n    for (std::uint32_t read = 0; read < read_count; ++read) {\n        pod5::ReadData read_data;\n        if (!load_struct_row_into_read_data(\n                file->writer, read_data, struct_version, row_data, read))\n        {\n            return g_pod5_error_no;\n        }\n\n        POD5_C_RETURN_NOT_OK(file->writer->add_complete_read(\n            read_data, gsl::make_span(signal[read], signal_size[read])));\n    }\n\n    return POD5_OK;\n}\n\npod5_error_t pod5_add_reads_data_pre_compressed(\n    Pod5FileWriter_t * file,\n    uint32_t read_count,\n    uint16_t struct_version,\n    void const * row_data,\n    char const *** compressed_signal,\n    size_t const ** compressed_signal_size,\n    uint32_t const ** sample_counts,\n    size_t const * signal_chunk_count)\n{\n    pod5_reset_error();\n\n    if (!check_file_not_null(file)) {\n        return g_pod5_error_no;\n    }\n    if (read_count == 0) {\n        return POD5_OK;\n    }\n    if (!check_read_data_struct(struct_version, row_data) || !check_not_null(compressed_signal)\n        || !check_not_null(compressed_signal_size) || !check_not_null(sample_counts)\n        || !check_not_null(signal_chunk_count))\n    {\n        return g_pod5_error_no;\n    }\n    for (std::uint32_t read = 0; read < read_count; ++read) {\n        if (!check_not_null(compressed_signal[read])\n            || !check_not_null(compressed_signal_size[read])\n            || !check_not_null(sample_counts[read]))\n        {\n            return g_pod5_error_no;\n        }\n    }\n\n    for (std::uint32_t read = 0; read < read_count; ++read) {\n        pod5::ReadData read_data;\n        if (!load_struct_row_into_read_data(\n                file->writer, read_data, struct_version, row_data, read))\n        {\n            return g_pod5_error_no;\n        }\n\n        std::uint64_t total_sample_count = 0;\n        std::vector<std::uint64_t> signal_rows;\n        for (std::size_t i = 0; i < signal_chunk_count[read]; ++i) {\n            auto signal = compressed_signal[read][i];\n            auto signal_size = compressed_signal_size[read][i];\n            auto sample_count = sample_counts[read][i];\n            total_sample_count += sample_count;\n            POD5_C_ASSIGN_OR_RAISE(\n                auto row_id,\n                file->writer->add_pre_compressed_signal(\n                    read_data.read_id,\n                    gsl::make_span(signal, signal_size).as_span<std::uint8_t const>(),\n                    sample_count));\n            signal_rows.push_back(row_id);\n        }\n\n        POD5_C_RETURN_NOT_OK(file->writer->add_complete_read(\n            read_data, gsl::make_span(signal_rows), total_sample_count));\n    }\n    return POD5_OK;\n}\n\nsize_t pod5_vbz_compressed_signal_max_size(size_t sample_count)\n{\n    pod5_reset_error();\n\n    auto const compressed_size = pod5::compressed_signal_max_size(sample_count);\n    if (!compressed_size.ok()) {\n        // TODO: on MAJOR_VERSION bump change this to return an error code.\n        pod5_set_error(compressed_size.status());\n        return 0;\n    } else {\n        return compressed_size.ValueUnsafe();\n    }\n}\n\npod5_error_t pod5_vbz_compress_signal(\n    int16_t const * signal,\n    size_t signal_size,\n    char * compressed_signal_out,\n    size_t * compressed_signal_size)\n{\n    pod5_reset_error();\n\n    if (!check_not_null(signal) || !check_output_pointer_not_null(compressed_signal_out)\n        || !check_output_pointer_not_null(compressed_signal_size))\n    {\n        return g_pod5_error_no;\n    }\n\n    POD5_C_ASSIGN_OR_RAISE(\n        auto buffer,\n        pod5::compress_signal(gsl::make_span(signal, signal_size), arrow::system_memory_pool()));\n\n    if ((std::size_t)buffer->size() > *compressed_signal_size) {\n        pod5_set_error(\n            pod5::Status::Invalid(\n                \"Compressed signal size (\",\n                buffer->size(),\n                \") is greater than provided buffer size (\",\n                compressed_signal_size,\n                \")\"));\n        return g_pod5_error_no;\n    }\n\n    std::copy(buffer->data(), buffer->data() + buffer->size(), compressed_signal_out);\n    *compressed_signal_size = buffer->size();\n\n    return POD5_OK;\n}\n\npod5_error_t pod5_vbz_decompress_signal(\n    char const * compressed_signal,\n    size_t compressed_signal_size,\n    size_t sample_count,\n    int16_t * signal_out)\n{\n    pod5_reset_error();\n\n    if (!check_not_null(compressed_signal) || !check_output_pointer_not_null(signal_out)) {\n        return g_pod5_error_no;\n    }\n\n    auto const in_span =\n        gsl::make_span(compressed_signal, compressed_signal_size).as_span<std::uint8_t const>();\n    auto out_span = gsl::make_span(signal_out, sample_count);\n    POD5_C_RETURN_NOT_OK(pod5::decompress_signal(in_span, arrow::system_memory_pool(), out_span));\n\n    return POD5_OK;\n}\n\npod5_error_t pod5_format_read_id(read_id_t const read_id, char * read_id_string)\n{\n    pod5_reset_error();\n\n    if (!check_not_null(read_id) || !check_output_pointer_not_null(read_id_string)) {\n        return g_pod5_error_no;\n    }\n\n    auto * uuid_data = reinterpret_cast<pod5::Uuid const *>(read_id);\n    uuid_data->write_to(read_id_string);\n    read_id_string[36] = '\\0';\n\n    return POD5_OK;\n}\n}\n"
  },
  {
    "path": "c++/pod5_format/c_api.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n\n#include <stddef.h>\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifndef _WIN32\n#define POD5_DEPRECATED __attribute__((deprecated))\n#elif (__STDC_VERSION__ >= 202000)\n#define POD5_DEPRECATED [[deprecated]]\n#else\n#define POD5_DEPRECATED\n#endif\n\n/// All functions are thread safe unless otherwise stated. Types may be used by multiple\n/// threads as long as the functions being called only take them by const pointer.\n\nstruct Pod5FileReader;\ntypedef struct Pod5FileReader Pod5FileReader_t;\nstruct Pod5FileWriter;\ntypedef struct Pod5FileWriter Pod5FileWriter_t;\nstruct Pod5ReadRecordBatch;\ntypedef struct Pod5ReadRecordBatch Pod5ReadRecordBatch_t;\n\n//---------------------------------------------------------------------------------------------------------------------\n// Error management\n//---------------------------------------------------------------------------------------------------------------------\n\n/// \\brief Integer error codes.\n/// \\note Taken from the arrow status enum.\nenum pod5_error {\n    POD5_OK = 0,\n    POD5_ERROR_OUTOFMEMORY = 1,\n    POD5_ERROR_KEYERROR = 2,\n    POD5_ERROR_TYPEERROR = 3,\n    POD5_ERROR_INVALID = 4,\n    POD5_ERROR_IOERROR = 5,\n    POD5_ERROR_CAPACITYERROR = 6,\n    POD5_ERROR_INDEXERROR = 7,\n    POD5_ERROR_CANCELLED = 8,\n    POD5_ERROR_UNKNOWNERROR = 9,\n    POD5_ERROR_NOTIMPLEMENTED = 10,\n    POD5_ERROR_SERIALIZATIONERROR = 11,\n    POD5_ERROR_STRING_NOT_LONG_ENOUGH = 12,\n};\ntypedef enum pod5_error pod5_error_t;\n\n/// \\brief Get the most recent error number from all pod5 api's on the current thread.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_get_error_no();\n/// \\brief Get the most recent error description string from all pod5 api's on the current thread.\n/// \\note The string's lifetime is internally managed, a caller should not free it.\nPOD5_FORMAT_EXPORT char const * pod5_get_error_string();\n\n//---------------------------------------------------------------------------------------------------------------------\n// Global state\n//---------------------------------------------------------------------------------------------------------------------\n\n/// \\brief Initialise and register global pod5 types\nPOD5_FORMAT_EXPORT pod5_error_t pod5_init();\n/// \\brief Terminate global pod5 types\nPOD5_FORMAT_EXPORT pod5_error_t pod5_terminate();\n\n//---------------------------------------------------------------------------------------------------------------------\n// Shared Structures\n//---------------------------------------------------------------------------------------------------------------------\n\nenum pod5_end_reason {\n    POD5_END_REASON_UNKNOWN = 0,\n    POD5_END_REASON_MUX_CHANGE = 1,\n    POD5_END_REASON_UNBLOCK_MUX_CHANGE = 2,\n    POD5_END_REASON_DATA_SERVICE_UNBLOCK_MUX_CHANGE = 3,\n    POD5_END_REASON_SIGNAL_POSITIVE = 4,\n    POD5_END_REASON_SIGNAL_NEGATIVE = 5,\n    POD5_END_REASON_API_REQUEST = 6,\n    POD5_END_REASON_DEVICE_DATA_ERROR = 7,\n    POD5_END_REASON_ANALYSIS_CONFIG_CHANGE = 8,\n    POD5_END_REASON_PAUSED = 9\n};\ntypedef enum pod5_end_reason pod5_end_reason_t;\n\ntypedef uint16_t run_info_index_t;\n\ntypedef uint8_t read_id_t[16];\ntypedef uint8_t run_id_t[16];\n\n// Single entry of read data:\nstruct ReadBatchRowInfoV3 {\n    // The read id data, in binary form.\n    read_id_t read_id;\n\n    // Read number for the read.\n    uint32_t read_number;\n    // Start sample for the read.\n    uint64_t start_sample;\n    // Median before level.\n    float median_before;\n\n    // Channel for the read.\n    uint16_t channel;\n    // Well for the read.\n    uint8_t well;\n    // Dictionary index for the pore type.\n    int16_t pore_type;\n    // Calibration offset type for the read.\n    float calibration_offset;\n    // Palibration type for the read.\n    float calibration_scale;\n    // End reason index for the read.\n    int16_t end_reason;\n    // Was the end reason for the read forced (0 for false, 1 for true).\n    uint8_t end_reason_forced;\n    // Dictionary index for run id for the read, can be used to look up run info.\n    int16_t run_info;\n\n    // Number of minknow events that the read contains\n    uint64_t num_minknow_events;\n\n    // Scale/Shift for tracked read scaling values (based on previous reads)\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED float tracked_scaling_scale;\n    POD5_DEPRECATED float tracked_scaling_shift;\n\n    // Scale/Shift for predicted read scaling values (based on this read's raw signal)\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED float predicted_scaling_scale;\n    POD5_DEPRECATED float predicted_scaling_shift;\n\n    // How many reads have been selected prior to this read on the channel-well since it was made active.\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED uint32_t num_reads_since_mux_change;\n    // How many seconds have passed since the channel-well was made active\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED float time_since_mux_change;\n\n    // Number of signal row entries for the read.\n    int64_t signal_row_count;\n\n    // The length of the read in samples.\n    uint64_t num_samples;\n};\n\n// Single entry of read data:\nstruct ReadBatchRowInfoV4 {\n    // The read id data, in binary form.\n    read_id_t read_id;\n\n    // Read number for the read.\n    uint32_t read_number;\n    // Start sample for the read.\n    uint64_t start_sample;\n    // Median before level.\n    float median_before;\n\n    // Channel for the read.\n    uint16_t channel;\n    // Well for the read.\n    uint8_t well;\n    // Dictionary index for the pore type.\n    int16_t pore_type;\n    // Calibration offset type for the read.\n    float calibration_offset;\n    // Palibration type for the read.\n    float calibration_scale;\n    // End reason index for the read.\n    int16_t end_reason;\n    // Was the end reason for the read forced (0 for false, 1 for true).\n    uint8_t end_reason_forced;\n    // Dictionary index for run id for the read, can be used to look up run info.\n    int16_t run_info;\n\n    // Number of minknow events that the read contains\n    uint64_t num_minknow_events;\n\n    // Scale/Shift for tracked read scaling values (based on previous reads)\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED float tracked_scaling_scale;\n    POD5_DEPRECATED float tracked_scaling_shift;\n\n    // Scale/Shift for predicted read scaling values (based on this read's raw signal)\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED float predicted_scaling_scale;\n    POD5_DEPRECATED float predicted_scaling_shift;\n\n    // How many reads have been selected prior to this read on the channel-well since it was made active.\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED uint32_t num_reads_since_mux_change;\n    // How many seconds have passed since the channel-well was made active\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED float time_since_mux_change;\n\n    // Number of signal row entries for the read.\n    int64_t signal_row_count;\n\n    // The length of the read in samples.\n    uint64_t num_samples;\n\n    // The level of the pore.\n    float open_pore_level;\n};\n\n// Typedef for latest batch row info structure.\ntypedef struct ReadBatchRowInfoV4 ReadBatchRowInfo_t;\n\nstruct POD5_DEPRECATED ReadBatchRowInfoArrayV3 {\n    // The read id data, in binary form.\n    read_id_t const * read_id;\n\n    // Read number for the read.\n    uint32_t const * read_number;\n    // Start sample for the read.\n    uint64_t const * start_sample;\n    // Median before level.\n    float const * median_before;\n\n    // Channel for the read.\n    uint16_t const * channel;\n    // Well for the read.\n    uint8_t const * well;\n    // Pore type for the read.\n    int16_t const * pore_type;\n    // Calibration offset type for the read.\n    float const * calibration_offset;\n    // Palibration type for the read.\n    float const * calibration_scale;\n    // End reason type for the read.\n    pod5_end_reason_t const * end_reason;\n    // Was the end reason for the read forced (0 for false, 1 for true).\n    uint8_t const * end_reason_forced;\n    // Run info type for the read.\n    int16_t const * run_info_id;\n\n    // Number of minknow events that the read contains\n    uint64_t const * num_minknow_events;\n\n    // Scale/Shift for tracked read scaling values (based on previous reads)\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED float const * tracked_scaling_scale;\n    POD5_DEPRECATED float const * tracked_scaling_shift;\n\n    // Scale/Shift for predicted read scaling values (based on this read's raw signal)\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED float const * predicted_scaling_scale;\n    POD5_DEPRECATED float const * predicted_scaling_shift;\n\n    // How many reads have been selected prior to this read on the channel-well since it was made active.\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED uint32_t const * num_reads_since_mux_change;\n    // How many seconds have passed since the channel-well was made active\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED float const * time_since_mux_change;\n};\n\n// Array of read data:\nstruct ReadBatchRowInfoArrayV4 {\n    // The read id data, in binary form.\n    read_id_t const * read_id;\n\n    // Read number for the read.\n    uint32_t const * read_number;\n    // Start sample for the read.\n    uint64_t const * start_sample;\n    // Median before level.\n    float const * median_before;\n\n    // Channel for the read.\n    uint16_t const * channel;\n    // Well for the read.\n    uint8_t const * well;\n    // Pore type for the read.\n    int16_t const * pore_type;\n    // Calibration offset type for the read.\n    float const * calibration_offset;\n    // Palibration type for the read.\n    float const * calibration_scale;\n    // End reason type for the read.\n    pod5_end_reason_t const * end_reason;\n    // Was the end reason for the read forced (0 for false, 1 for true).\n    uint8_t const * end_reason_forced;\n    // Run info type for the read.\n    int16_t const * run_info_id;\n\n    // Number of minknow events that the read contains\n    uint64_t const * num_minknow_events;\n\n    // Scale/Shift for tracked read scaling values (based on previous reads)\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED float const * tracked_scaling_scale;\n    POD5_DEPRECATED float const * tracked_scaling_shift;\n\n    // Scale/Shift for predicted read scaling values (based on this read's raw signal)\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED float const * predicted_scaling_scale;\n    POD5_DEPRECATED float const * predicted_scaling_shift;\n\n    // How many reads have been selected prior to this read on the channel-well since it was made active.\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED uint32_t const * num_reads_since_mux_change;\n    // How many seconds have passed since the channel-well was made active\n    // DEPRECATED: will be removed in 0.4.0\n    POD5_DEPRECATED float const * time_since_mux_change;\n\n    // The level of the pore.\n    float const * open_pore_level;\n};\n\n// Typedef for latest batch row info structure.\ntypedef struct ReadBatchRowInfoArrayV4 ReadBatchRowInfoArray_t;\n\n#define READ_BATCH_ROW_INFO_VERSION_0 0\n// Addition of num_minknow_events fields, scaling fields.\n#define READ_BATCH_ROW_INFO_VERSION_1 1\n// Addition of num_samples fields.\n#define READ_BATCH_ROW_INFO_VERSION_2 2\n// Flattening of read structures.\n#define READ_BATCH_ROW_INFO_VERSION_3 3\n// Introduction of new open_pore_level field.\n#define READ_BATCH_ROW_INFO_VERSION_4 4\n// Latest available version.\n#define READ_BATCH_ROW_INFO_VERSION READ_BATCH_ROW_INFO_VERSION_4\n\n//---------------------------------------------------------------------------------------------------------------------\n// Reading files\n//---------------------------------------------------------------------------------------------------------------------\n\n// Options to control how a file is written.\nstruct Pod5ReaderOptions {\n    /// \\brief Disable file mapping into memory. Reduces memory usage of pod5 files, at the expense\n    ///        of the underlying arrow file loading into memory on demand.\n    char force_disable_file_mapping;\n};\ntypedef struct Pod5ReaderOptions Pod5ReaderOptions_t;\n\n/// \\brief Open a file reader with default options.\n/// \\param filename The filename of the pod5 file.\n/// \\see pod5_open_file_options\nPOD5_FORMAT_EXPORT Pod5FileReader_t * pod5_open_file(char const * filename);\n\n/// \\brief Open a file reader\n/// \\param filename         The filename of the pod5 file.\n/// \\param options          The options to use when opening the file.\n/// \\return A reader, or null on error. The reason for the error can be queried with\n///         pod5_get_error_no() and pod5_get_error_string().\nPOD5_FORMAT_EXPORT Pod5FileReader_t * pod5_open_file_options(\n    char const * filename,\n    Pod5ReaderOptions_t const * options);\n\n/// \\brief Close a file reader, releasing all memory held by the reader.\n/// \\param file A previously opened reader.\n/// \\note Any references to \\a file or its components are no longer valid after this call.\n/// \\note It is safe to call this with a null \\a file.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_close_and_free_reader(Pod5FileReader_t * file);\n\nstruct FileInfo {\n    read_id_t file_identifier;\n\n    struct Version {\n        uint16_t major;\n        uint16_t minor;\n        uint16_t revision;\n    } version;\n};\ntypedef struct FileInfo FileInfo_t;\n\n/// \\brief Find info about a file.\n/// \\param[in]  file        The file to be queried.\n/// \\param[out] file_info   The info read from the file.\nPOD5_FORMAT_EXPORT pod5_error_t\npod5_get_file_info(Pod5FileReader_t const * file, FileInfo_t * file_info);\n\nstruct EmbeddedFileData {\n    // The embedded file name - note this may not be the original file name, if the file has been migrated.\n    // This pointer will remain valid until the next pod5 api call on the associated reader.\n    char const * file_name;\n    size_t offset;\n    size_t length;\n};\ntypedef struct EmbeddedFileData EmbeddedFileData_t;\n\n/// \\brief Find the location of the read table data\n/// \\param[in]  file        The file to be queried.\n/// \\param[out] file_data   The output read table file data.\nPOD5_FORMAT_EXPORT pod5_error_t\npod5_get_file_read_table_location(Pod5FileReader_t const * file, EmbeddedFileData_t * file_data);\n\n/// \\brief Find the location of the signal table data\n/// \\param[in]  file        The file to be queried.\n/// \\param[out] file_data   The output signal table file data.\nPOD5_FORMAT_EXPORT pod5_error_t\npod5_get_file_signal_table_location(Pod5FileReader_t const * file, EmbeddedFileData_t * file_data);\n\n/// \\brief Find the location of the run info table data\n/// \\param[in]  file        The file to be queried.\n/// \\param[out] file_data   The output signal table file data.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_get_file_run_info_table_location(\n    Pod5FileReader_t const * file,\n    EmbeddedFileData_t * file_data);\n\n/// \\brief Find the number of reads in the file.\n/// \\param[in]  reader  The file reader to read from\n/// \\param[out] count   The number of reads in the file\nPOD5_FORMAT_EXPORT pod5_error_t\npod5_get_read_count(Pod5FileReader_t const * reader, size_t * count);\n\n/// \\brief Grab the read_id's from the file.\n/// \\param[in]  reader        The file reader to read from.\n/// \\param      count         The number of read_id's allocated in [read_ids], an error is raised if the count is not greater or equal to pod5_get_read_count.\n/// \\param[out] read_ids      The read id's written in a contiguous array.\nPOD5_FORMAT_EXPORT pod5_error_t\npod5_get_read_ids(Pod5FileReader_t const * reader, size_t count, read_id_t * read_ids);\n\n/// \\brief Plan the most efficient route through the data for the given read ids\n/// \\param[in]  file                The file to be queried.\n/// \\param[in]  read_id_array       The read id array (contiguous array, 16 bytes per id).\n/// \\param      read_id_count       The number of read ids.\n/// \\param[out] batch_counts        The number of rows per batch that need to be visited (rows listed in batch_rows),\n///                                 input array length should be the number of read table batches.\n/// \\param[out] batch_rows          Rows to visit per batch, packed into one array. Offsets into this array from\n///                                 [batch_counts] provide the per-batch row data. Input array length should\n///                                 equal read_id_count.\n/// \\param[out] find_success_count  The number of requested read ids that were found.\n/// \\note The output arrays are sorted in file storage order, to improve read efficiency.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_plan_traversal(\n    Pod5FileReader_t const * file,\n    uint8_t const * read_id_array,\n    size_t read_id_count,\n    uint32_t * batch_counts,\n    uint32_t * batch_rows,\n    size_t * find_success_count);\n\n/// \\brief Find the number of read batches in the file.\n/// \\param[out] count   The number of read batches in the file\n/// \\param[in]  reader  The file reader to read from\nPOD5_FORMAT_EXPORT pod5_error_t\npod5_get_read_batch_count(size_t * count, Pod5FileReader_t const * reader);\n\n/// \\brief Get a read batch from the file.\n/// \\param[out] batch   The extracted batch.\n/// \\param[in]  reader  The file reader to read from\n/// \\param      index   The index of the batch to read.\n/// \\note Batches returned from this API must be freed using #pod5_free_read_batch\nPOD5_FORMAT_EXPORT pod5_error_t\npod5_get_read_batch(Pod5ReadRecordBatch_t ** batch, Pod5FileReader_t const * reader, size_t index);\n\n/// \\brief Release a read batch when it is not longer used.\n/// \\param batch The batch to release.\n/// \\note Any references to \\a batch or its components are no longer valid after this call.\n/// \\note It is safe to call this with a null \\a batch.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_free_read_batch(Pod5ReadRecordBatch_t * batch);\n\n/// \\brief Find the number of rows in a batch.\n/// \\param[out] count   The number of rows in the batch.\n/// \\param[in]  batch   The batch to query the number of rows for.\nPOD5_FORMAT_EXPORT pod5_error_t\npod5_get_read_batch_row_count(size_t * count, Pod5ReadRecordBatch_t const * batch);\n\n/// \\brief Find the info for a row in a read batch.\n/// \\param[in]  batch               The read batch to query.\n/// \\param      row                 The row index to query.\n/// \\param      struct_version      The version of the struct being passed in, calling code\n///                                 should use [READ_BATCH_ROW_INFO_VERSION].\n/// \\param[out] row_data            The data for reading into, should be a pointer to ReadBatchRowInfo_t.\n/// \\param[out] read_table_version  The table version read from the file, will indicate which fields should be available.\n///                                 See READ_BATCH_ROW_INFO_VERSION and ReadBatchRowInfo_t above for corresponding fields.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_get_read_batch_row_info_data(\n    Pod5ReadRecordBatch_t const * batch,\n    size_t row,\n    uint16_t struct_version,\n    void * row_data,\n    uint16_t * read_table_version);\n\n/// \\brief Find the signal indices for a row in a read batch.\n/// \\param[in]  batch                       The read batch to query.\n/// \\param      row                         The row index to query.\n/// \\param      signal_row_indices_count    Number of entries in the signal_row_indices array.\n/// \\param[out] signal_row_indices          The signal row indices read out of the read row.\n/// \\note signal_row_indices_count Must equal signal_row_count returned from pod5_get_read_batch_row_info_data\n///       or an error is generated.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_get_signal_row_indices(\n    Pod5ReadRecordBatch_t const * batch,\n    size_t row,\n    int64_t signal_row_indices_count,\n    uint64_t * signal_row_indices);\n\nstruct CalibrationExtraData {\n    // The digitisation value used by the sequencer, equal to:\n    //\n    // adc_max - adc_min + 1\n    uint16_t digitisation;\n    // The range of the calibrated channel in pA.\n    float range;\n};\ntypedef struct CalibrationExtraData CalibrationExtraData_t;\n\n/// \\brief Find the extra calibration info for a row in a read batch.\n/// \\param[in]  batch                   The read batch to query.\n/// \\param      row                     The read row index.\n/// \\param[out] calibration_extra_data  Output location for the calibration data.\n/// \\note The values are computed from data held in the file, and written directly to the address provided, there is no need to release any data.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_get_calibration_extra_info(\n    Pod5ReadRecordBatch_t const * batch,\n    size_t row,\n    CalibrationExtraData_t * calibration_extra_data);\n\nstruct KeyValueData {\n    size_t size;\n    char const ** keys;\n    char const ** values;\n};\n\nstruct RunInfoDictData {\n    char const * acquisition_id;\n    int64_t acquisition_start_time_ms;\n    int16_t adc_max;\n    int16_t adc_min;\n    struct KeyValueData context_tags;\n    char const * experiment_name;\n    char const * flow_cell_id;\n    char const * flow_cell_product_code;\n    char const * protocol_name;\n    char const * protocol_run_id;\n    int64_t protocol_start_time_ms;\n    char const * sample_id;\n    uint16_t sample_rate;\n    char const * sequencing_kit;\n    char const * sequencer_position;\n    char const * sequencer_position_type;\n    char const * software;\n    char const * system_name;\n    char const * system_type;\n    struct KeyValueData tracking_id;\n};\ntypedef struct RunInfoDictData RunInfoDictData_t;\n\n/// \\brief Find the run info for a row in a read batch.\n/// \\param[in]  batch               The read batch to query.\n/// \\param      run_info            The run info index to query from the passed batch.\n/// \\param[out] run_info_data       Output location for the run info data.\n/// \\note The returned run_info value should be released using pod5_free_run_info when it is no longer used.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_get_run_info(\n    Pod5ReadRecordBatch_t const * batch,\n    int16_t run_info,\n    RunInfoDictData_t ** run_info_data);\n\n/// \\brief Find the run info for a row in a file.\n/// \\param[in]  file                The file to query.\n/// \\param      run_info_index      The run info index to query from the passed file.\n/// \\param[out] run_info_data       Output location for the run info data.\n/// \\note The returned run_info value should be released using pod5_free_run_info when it is no longer used.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_get_file_run_info(\n    Pod5FileReader_t const * file,\n    run_info_index_t run_info_index,\n    RunInfoDictData_t ** run_info_data);\n\n/// \\brief Release a RunInfoDictData struct after use.\n/// \\param run_info_data The run info to release.\n/// \\note Any references to \\a run_info_data or its components are no longer valid after this call.\n/// \\note It is safe to call this with a null \\a run_info_data.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_free_run_info(RunInfoDictData_t * run_info_data);\n\n/// \\brief Release a RunInfoDictData struct after use.\n/// \\deprecated\nPOD5_FORMAT_EXPORT POD5_DEPRECATED pod5_error_t\npod5_release_run_info(RunInfoDictData_t * run_info_data);\n\n/// \\brief Find the run info for a row in a read file.\n/// \\param[in]  file                The file to query.\n/// \\param[out] run_info_count      The number of run info's that are present in they queried file\nPOD5_FORMAT_EXPORT pod5_error_t\npod5_get_file_run_info_count(Pod5FileReader_t const * file, run_info_index_t * run_info_count);\n\n/// \\brief Find the end reason for a row in a read batch.\n/// \\param[in]      batch                           The read batch to query.\n/// \\param          end_reason                      The end reason index to query from the passed batch.\n/// \\param[out]     end_reason_value                The enum value for end reason.\n/// \\param[out]     end_reason_string_value         Output location for the string value for the end reason.\n/// \\param[in,out]  end_reason_string_value_size    Size of [end_reason_string_value], the number of characters written (including 1 for null character) is placed in this value on return.\n/// \\note If the string input is not long enough POD5_ERROR_STRING_NOT_LONG_ENOUGH is returned.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_get_end_reason(\n    Pod5ReadRecordBatch_t const * batch,\n    int16_t end_reason,\n    pod5_end_reason_t * end_reason_value,\n    char * end_reason_string_value,\n    size_t * end_reason_string_value_size);\n\n/// \\brief Find the pore type for a row in a read batch.\n/// \\param[in]      batch                           The read batch to query.\n/// \\param          pore_type                       The pore type index to query from the passed batch.\n/// \\param[out]     pore_type_string_value          Output location for the string value for the pore type.\n/// \\param[in,out]  pore_type_string_value_size     Size of [pore_type_string_value], the number of characters written (including 1 for null character) is placed in this value on return.\n/// \\note If the string input is not long enough POD5_ERROR_STRING_NOT_LONG_ENOUGH is returned.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_get_pore_type(\n    Pod5ReadRecordBatch_t const * batch,\n    int16_t pore_type,\n    char * pore_type_string_value,\n    size_t * pore_type_string_value_size);\n\nstruct SignalRowInfo {\n    size_t batch_index;\n    size_t batch_row_index;\n    uint32_t stored_sample_count;\n    size_t stored_byte_count;\n};\ntypedef struct SignalRowInfo SignalRowInfo_t;\n\n/// \\brief Find the info for a signal row in a reader.\n/// \\param[in]  reader                      The reader to query.\n/// \\param      signal_rows_count           The number of signal rows to query.\n/// \\param[in]  signal_rows                 The signal rows to query.\n/// \\param[out] signal_row_info             Pointers to the output signal row information (must be an array of size signal_rows_count)\nPOD5_FORMAT_EXPORT pod5_error_t pod5_get_signal_row_info(\n    Pod5FileReader_t const * reader,\n    size_t signal_rows_count,\n    uint64_t const * signal_rows,\n    SignalRowInfo_t ** signal_row_info);\n\n/// \\brief Release a list of signal row infos allocated by [pod5_get_signal_row_info].\n/// \\param      signal_rows_count           The number of signal rows to release.\n/// \\param      signal_row_info             The signal row infos to release.\n/// \\note Calls to pod5_free_signal_row_info must be 1:1 with [pod5_get_signal_row_info], you cannot free part of the returned data.\nPOD5_FORMAT_EXPORT pod5_error_t\npod5_free_signal_row_info(size_t signal_rows_count, SignalRowInfo_t ** signal_row_info);\n\n/// \\brief Find the info for a signal row in a reader.\n/// \\param[in]  reader          The reader to query.\n/// \\param[in]  row_info        The signal row info batch index to query data for.\n/// \\param      sample_count    The number of samples allocated in [sample_data] (must equal the length of signal data in the row).\n/// \\param[out] sample_data     The output location for the queried samples.\n/// \\note The signal data is allocated by the caller and should be released as appropriate by the caller.\n/// \\todo MAJOR_VERSION Rename to include \"chunk\" or \"row\" or similar to indicate this gets only part of read signal.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_get_signal(\n    Pod5FileReader_t const * reader,\n    SignalRowInfo_t const * row_info,\n    size_t sample_count,\n    int16_t * sample_data);\n\n/// \\brief Find the sample count for a full read.\n/// \\param[in]  reader          The reader to query.\n/// \\param[in]  batch           The read batch to query.\n/// \\param      batch_row       The read row to query data for.\n/// \\param[out] sample_count    The number of samples in the read - including all chunks of raw data.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_get_read_complete_sample_count(\n    Pod5FileReader_t const * reader,\n    Pod5ReadRecordBatch_t const * batch,\n    size_t batch_row,\n    size_t * sample_count);\n\n/// \\brief Find the signal for a full read.\n/// \\param[in]  reader          The reader to query.\n/// \\param[in]  batch           The read batch to query.\n/// \\param      batch_row       The read row to query data for.\n/// \\param      sample_count    The number of samples allocated in [signal] (must equal the length of signal data in the queryied read row).\n/// \\param[out] signal          The output location for the queried samples.\n/// \\note The signal data is allocated by the caller and should be released as appropriate by the caller.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_get_read_complete_signal(\n    Pod5FileReader_t const * reader,\n    Pod5ReadRecordBatch_t const * batch,\n    size_t batch_row,\n    size_t sample_count,\n    int16_t * signal);\n\n//---------------------------------------------------------------------------------------------------------------------\n// Writing files\n//---------------------------------------------------------------------------------------------------------------------\n\n// Signal compression options.\nenum CompressionOption {\n    /// \\brief Use the default signal compression option.\n    DEFAULT_SIGNAL_COMPRESSION = 0,\n    /// \\brief Use vbz to compress read signals in tables.\n    VBZ_SIGNAL_COMPRESSION = 1,\n    /// \\brief Write signals uncompressed to tables.\n    UNCOMPRESSED_SIGNAL = 2,\n};\n\n// Options to control how a file is written.\nstruct Pod5WriterOptions {\n    /// \\brief Maximum number of samples to place in one signal record in the signals table.\n    /// \\note Use zero to use default value.\n    uint32_t max_signal_chunk_size;\n    /// \\brief Signal type to write to the signals table.\n    /// \\note Use 'DEFAULT_SIGNAL_COMPRESSION' to use default value.\n    int8_t signal_compression_type;\n\n    /// \\brief The size of each batch written for the signal table (zero for default).\n    size_t signal_table_batch_size;\n    /// \\brief The size of each batch written for the reads table (zero for default).\n    size_t read_table_batch_size;\n};\ntypedef struct Pod5WriterOptions Pod5WriterOptions_t;\n\n/// \\brief Create a new pod5 file using specified filenames and options.\n/// \\param filename         The filename of the pod5 file.\n/// \\param writer_name      A descriptive string for the user software writing this file.\n/// \\param options          Options controlling how the file will be written.\n/// \\return A writer, or null on error. The reason for the error can be queried with\n///         pod5_get_error_no() and pod5_get_error_string().\nPOD5_FORMAT_EXPORT Pod5FileWriter_t * pod5_create_file(\n    char const * filename,\n    char const * writer_name,\n    Pod5WriterOptions_t const * options);\n\n/// \\brief Close a file writer, releasing all memory held by the writer.\n/// \\param file A previously opened writer.\n/// \\note Any references to \\a file or its components are no longer valid after this call.\n/// \\note It is safe to call this with a null \\a file.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_close_and_free_writer(Pod5FileWriter_t * file);\n\n/// \\brief Add a new pore type to the file.\n/// \\param[out] pore_index  The index of the added pore.\n/// \\param      file        The file to add the new pore type to.\n/// \\param      pore_type   The pore type string for the pore.\nPOD5_FORMAT_EXPORT pod5_error_t\npod5_add_pore(int16_t * pore_index, Pod5FileWriter_t * file, char const * pore_type);\n\n/// \\brief Add a new run info to the file, containing tracking information about a sequencing run.\n/// \\param[out] run_info_index              The index of the added run_info.\n/// \\param      file                        The file to add the new pore type to.\n/// \\param      acquisition_id              The offset parameter for the calibration.\n/// \\param      acquisition_start_time_ms   Milliseconds after unix epoch when the acquisition was started.\n/// \\param      adc_max                     Maximum ADC value supported by this hardware.\n/// \\param      adc_min                     Minimum ADC value supported by this hardware.\n/// \\param      context_tags_count          Number of entries in the context tags map.\n/// \\param      context_tags_keys           Array of strings used as keys into the context tags map (must have context_tags_count entries).\n/// \\param      context_tags_values         Array of strings used as values in the context tags map (must have context_tags_count entries).\n/// \\param      experiment_name             Name given by the user to the group including this protocol.\n/// \\param      flow_cell_id                Id for the flow cell used in the run.\n/// \\param      flow_cell_product_code      Product code for the flow cell used in the run.\n/// \\param      protocol_name               Name given by the user to the protocol.\n/// \\param      protocol_run_id             Run id for the protocol.\n/// \\param      protocol_start_time_ms      Milliseconds after unix epoch when the protocol was started.\n/// \\param      sample_id                   Name given by the user for sample id.\n/// \\param      sample_rate                 Sample rate of the run.\n/// \\param      sequencing_kit              Sequencing kit used in the run.\n/// \\param      sequencer_position          Sequencer position used in the run.\n/// \\param      sequencer_position_type     Sequencer position type used in the run.\n/// \\param      software                    Name of the software used to produce the run.\n/// \\param      system_name                 Name of the system used to produce the run.\n/// \\param      system_type                 Type of the system used to produce the run.\n/// \\param      tracking_id_count           Number of entries in the tracking id map.\n/// \\param      tracking_id_keys            Array of strings used as keys into the tracking id map (must have tracking_id_count entries).\n/// \\param      tracking_id_values          Array of strings used as values in the tracking id map (must have tracking_id_count entries).\nPOD5_FORMAT_EXPORT pod5_error_t pod5_add_run_info(\n    int16_t * run_info_index,\n    Pod5FileWriter_t * file,\n    char const * acquisition_id,\n    int64_t acquisition_start_time_ms,\n    int16_t adc_max,\n    int16_t adc_min,\n    size_t context_tags_count,\n    char const ** context_tags_keys,\n    char const ** context_tags_values,\n    char const * experiment_name,\n    char const * flow_cell_id,\n    char const * flow_cell_product_code,\n    char const * protocol_name,\n    char const * protocol_run_id,\n    int64_t protocol_start_time_ms,\n    char const * sample_id,\n    uint16_t sample_rate,\n    char const * sequencing_kit,\n    char const * sequencer_position,\n    char const * sequencer_position_type,\n    char const * software,\n    char const * system_name,\n    char const * system_type,\n    size_t tracking_id_count,\n    char const ** tracking_id_keys,\n    char const ** tracking_id_values);\n\n/// \\brief Add a read to the file.\n///\n/// For each read `r`, where `0 <= r < read_count`:\n/// - `row_data->field[r]` describes a field of the read metadata\n/// - `signal[r]` is the raw signal data for the read\n/// - `signal_size[r]` is the length of `signal[r]` (in samples, not in bytes)\n///\n/// \\param      file            The file to add the reads to.\n/// \\param      read_count      The number of reads to add with this call.\n/// \\param      struct_version  The version of the struct of [row_data] being filled, use READ_BATCH_ROW_INFO_VERSION.\n/// \\param      row_data        The array data for injecting into the file, should be ReadBatchRowInfoArray_t.\n///                             All fields of the array must have length [read_count].\n/// \\param      signal          The signal data for the reads.\n/// \\param      signal_size     The number of samples in the signal data.\n///                             This must be an array of length [read_count].\nPOD5_FORMAT_EXPORT pod5_error_t pod5_add_reads_data(\n    Pod5FileWriter_t * file,\n    uint32_t read_count,\n    uint16_t struct_version,\n    void const * row_data,\n    int16_t const ** signal,\n    uint32_t const * signal_size);\n\n/// \\brief Add a read to the file, with pre compressed signal chunk sections.\n///\n/// Consider using the simpler [pod5_add_reads_data] unless you have performance requirements that demand\n/// more control over compression and chunking.\n///\n/// Data should be compressed using [pod5_vbz_compress_signal].\n///\n/// For each read `r`, where `0 <= r < read_count`:\n/// - `row_data->field[r]` describes a field of the read metadata\n/// - `signal_chunk_count[r]` is the number of signal chunks\n/// - for each signal chunk `i` where `0 <= i < signal_chunk_count[r]`:\n///   - `sample_counts[r][i]` is the number of samples in the chunk (ie: the size of the uncompressed data in\n///     samples, not in bytes)\n///   - `compressed_signal[r][i]` is the compressed data\n///   - `compressed_signal_size[r][i]` is the length of the compressed data at `compressed_signal[r][i]`\n///\n/// \\param      file                    The file to add the read to.\n/// \\param      read_count              The number of reads to add with this call.\n/// \\param      struct_version          The version of the struct of [row_data] being filled, use READ_BATCH_ROW_INFO_VERSION.\n/// \\param      row_data                The array data for injecting into the file, should be ReadBatchRowInfoArray_t.\n///                                     All fields of the array must have length [read_count].\n/// \\param      compressed_signal       The signal chunks data for the read.\n/// \\param      compressed_signal_size  The sizes (in bytes) of each signal chunk.\n/// \\param      sample_counts           The number of samples of each signal chunk. In other words, it is the *uncompressed* size of the\n///                                     corresponding [compressed_signal] array, in samples (not bytes!).\n/// \\param      signal_chunk_count      The number of sections of compressed signal.\n///                                     This must be an array of length [read_count].\nPOD5_FORMAT_EXPORT pod5_error_t pod5_add_reads_data_pre_compressed(\n    Pod5FileWriter_t * file,\n    uint32_t read_count,\n    uint16_t struct_version,\n    void const * row_data,\n    char const *** compressed_signal,\n    size_t const ** compressed_signal_size,\n    uint32_t const ** sample_counts,\n    size_t const * signal_chunk_count);\n\n/// \\brief Find the max size of a compressed array of samples.\n/// \\param sample_count The number of samples in the source signal.\n/// \\return The max number of bytes required for the compressed signal, or 0 on error.\n///         The reason for the error can be queried with pod5_get_error_no() and\n///         pod5_get_error_string().\nPOD5_FORMAT_EXPORT size_t pod5_vbz_compressed_signal_max_size(size_t sample_count);\n\n/// \\brief VBZ compress an array of samples.\n/// \\param          signal                      The signal to compress.\n/// \\param          signal_size                 The number of samples to compress.\n/// \\param[out]     compressed_signal_out       The compressed signal.\n/// \\param[in,out]  compressed_signal_size      The number of compressed bytes, should be set to the size of compressed_signal_out on call.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_vbz_compress_signal(\n    int16_t const * signal,\n    size_t signal_size,\n    char * compressed_signal_out,\n    size_t * compressed_signal_size);\n\n/// \\brief VBZ decompress an array of samples.\n/// \\param          compressed_signal           The signal to decompress.\n/// \\param          compressed_signal_size      The number of compressed bytes, ie the size of compressed_signal in bytes.\n/// \\param          sample_count                The number of samples to decompress, ie the size of signal_out in samples.\n/// \\param[out]     signal_out                  The decompressed signal.\nPOD5_FORMAT_EXPORT pod5_error_t pod5_vbz_decompress_signal(\n    char const * compressed_signal,\n    size_t compressed_signal_size,\n    size_t sample_count,\n    int16_t * signal_out);\n\n//---------------------------------------------------------------------------------------------------------------------\n// Global state\n//---------------------------------------------------------------------------------------------------------------------\n\n/// \\brief Format a packed binary read id as a readable read id string:\n/// \\param          read_id           A 16 byte binary formatted UUID.\n/// \\param[out]     read_id_string    Output string containing the string formatted UUID (expects a string of at least 37 bytes, one null byte is written.)\nPOD5_FORMAT_EXPORT pod5_error_t pod5_format_read_id(read_id_t const read_id, char * read_id_string);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "c++/pod5_format/dictionary_writer.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/result.h\"\n\nnamespace arrow {\nclass Array;\n}\n\nnamespace pod5 {\n\nclass POD5_FORMAT_EXPORT DictionaryWriter {\npublic:\n    virtual ~DictionaryWriter() = default;\n\n    pod5::Result<std::shared_ptr<arrow::Array>> build_dictionary_array(\n        std::shared_ptr<arrow::Array> const & indices);\n    virtual pod5::Result<std::shared_ptr<arrow::Array>> get_value_array() = 0;\n    virtual std::size_t item_count() = 0;\n\n    bool is_valid(std::size_t value) { return value < item_count(); }\n};\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/expandable_buffer.h",
    "content": "#pragma once\n\n#include <arrow/buffer.h>\n#include <arrow/result.h>\n#include <gsl/gsl-lite.hpp>\n\n#include <cassert>\n\nnamespace pod5 {\n\ntemplate <typename T>\nclass ExpandableBuffer {\npublic:\n    static constexpr int EXPANSION_FACTOR = 2;\n\n    ExpandableBuffer(arrow::MemoryPool * pool = nullptr) { m_pool = pool; }\n\n    arrow::Status init_buffer(arrow::MemoryPool * pool)\n    {\n        m_pool = pool;\n        return clear();\n    }\n\n    std::size_t size() const\n    {\n        if (!m_buffer) {\n            return 0;\n        }\n        return m_buffer->size() / sizeof(T);\n    }\n\n    std::uint8_t * mutable_data() { return m_buffer->mutable_data(); }\n\n    std::shared_ptr<arrow::Buffer> get_buffer() const { return m_buffer; }\n\n    arrow::Status clear()\n    {\n        if (!m_buffer || m_buffer.use_count() > 1) {\n            ARROW_ASSIGN_OR_RAISE(m_buffer, arrow::AllocateResizableBuffer(0, m_pool));\n            return arrow::Status::OK();\n        } else {\n            return m_buffer->Resize(0, false);\n        }\n    }\n\n    gsl::span<T const> get_data_span() const\n    {\n        if (!m_buffer) {\n            return {};\n        }\n\n        return gsl::make_span(m_buffer->data(), m_buffer->size()).template as_span<T const>();\n    }\n\n    /// \\brief Append an object where you don't know the size up front.\n    /// \\param max_size The maximum possible size of the object to append.\n    /// \\param append_fn A function that appends the object to the buffer.\n    template <typename Callable>\n    arrow::Status append(std::size_t max_size, Callable append_fn)\n    {\n        auto const old_size = m_buffer->size();\n        ARROW_RETURN_NOT_OK(reserve(old_size + max_size));\n        auto const potential_buffer = gsl::make_span(m_buffer->mutable_data() + old_size, max_size);\n        ARROW_ASSIGN_OR_RAISE(auto final_size, append_fn(potential_buffer));\n        assert(final_size < max_size);\n        return resize(old_size + final_size);\n    }\n\n    arrow::Status append(T const & new_value)\n    {\n        auto const bytes_span =\n            gsl::make_span(&new_value, 1).template as_span<std::uint8_t const>();\n\n        return append_bytes(bytes_span);\n    }\n\n    arrow::Status append_array(gsl::span<T const> const & new_value_span)\n    {\n        auto const bytes_span = new_value_span.template as_span<std::uint8_t const>();\n\n        return append_bytes(bytes_span);\n    }\n\n    arrow::Status resize(std::int64_t new_size)\n    {\n        ARROW_RETURN_NOT_OK(reserve(new_size));\n        return m_buffer->Resize(new_size, false);\n    }\n\n    arrow::Status reserve(std::int64_t new_capacity)\n    {\n        assert(m_buffer);\n        auto const old_size = m_buffer->size();\n        if (m_buffer.use_count() > 1) {\n            std::shared_ptr<arrow::ResizableBuffer> buffer;\n            ARROW_ASSIGN_OR_RAISE(buffer, arrow::AllocateResizableBuffer(old_size, m_pool));\n\n            std::copy(m_buffer->data(), m_buffer->data() + old_size, buffer->mutable_data());\n            std::swap(m_buffer, buffer);\n        }\n\n        if (new_capacity > m_buffer->capacity()) {\n            ARROW_RETURN_NOT_OK(m_buffer->Reserve(new_capacity * EXPANSION_FACTOR));\n        }\n        return arrow::Status::OK();\n    }\n\nprivate:\n    arrow::Status append_bytes(gsl::span<std::uint8_t const> const & bytes_span)\n    {\n        auto old_size = 0;\n        if (!m_buffer) {\n            ARROW_ASSIGN_OR_RAISE(\n                m_buffer, arrow::AllocateResizableBuffer(bytes_span.size(), m_pool));\n        } else {\n            old_size = m_buffer->size();\n        }\n        auto const new_size = old_size + bytes_span.size();\n        ARROW_RETURN_NOT_OK(reserve(new_size));\n\n        ARROW_RETURN_NOT_OK(m_buffer->Resize(new_size, false));\n        std::copy(bytes_span.begin(), bytes_span.end(), m_buffer->mutable_data() + old_size);\n        return arrow::Status::OK();\n    }\n\n    std::shared_ptr<arrow::ResizableBuffer> m_buffer;\n    arrow::MemoryPool * m_pool = nullptr;\n};\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/file_output_stream.h",
    "content": "#pragma once\n\n#include <arrow/io/file.h>\n#include <arrow/status.h>\n\nnamespace pod5 {\n\nclass FileOutputStream : public arrow::io::OutputStream {\npublic:\n    virtual arrow::Status batch_complete() { return arrow::Status::OK(); }\n\n    virtual void set_file_start_offset(std::size_t val) {}\n};\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/file_reader.cpp",
    "content": "#include \"pod5_format/file_reader.h\"\n\n#include \"pod5_format/internal/combined_file_utils.h\"\n#include \"pod5_format/memory_pool.h\"\n#include \"pod5_format/migration/migration.h\"\n#include \"pod5_format/read_table_reader.h\"\n#include \"pod5_format/run_info_table_reader.h\"\n#include \"pod5_format/signal_table_reader.h\"\n\n#include <arrow/io/concurrency.h>\n#include <arrow/io/file.h>\n#include <arrow/ipc/reader.h>\n\n#include <cstdint>\n#include <iostream>\n#if defined(__APPLE__) || defined(__linux__) || defined(__unix__)\n#include <sys/stat.h>\n#endif\n\nnamespace pod5 {\n\nnamespace {\n#if defined(__APPLE__) || defined(__linux__) || defined(__unix__)\nconstexpr std::uint64_t kStatBlockBytes = S_BLKSIZE;\n// Issue warning if the majority of the file is missing to avoid false positives\nconstexpr double kMinMissingFractionThreshold = 0.8;\n\nvoid warn_if_stat_size_and_blocks_differ_significantly(std::string const & path)\n{\n    struct stat file_stat = {};\n    if (::stat(path.c_str(), &file_stat) != 0 || file_stat.st_size <= 0 || file_stat.st_blocks < 0)\n    {\n        return;\n    }\n\n    auto const logical_size_bytes = static_cast<std::uint64_t>(file_stat.st_size);\n    auto const allocated_size_bytes =\n        static_cast<std::uint64_t>(file_stat.st_blocks) * kStatBlockBytes;\n\n    if (allocated_size_bytes >= logical_size_bytes) {\n        return;\n    }\n\n    auto const missing_bytes = logical_size_bytes - allocated_size_bytes;\n    auto const missing_fraction =\n        static_cast<double>(missing_bytes) / static_cast<double>(logical_size_bytes);\n    if (missing_fraction < kMinMissingFractionThreshold) {\n        return;\n    }\n\n    std::cerr << \"Warning: POD5 file '\" << path << \"' has st_size=\" << logical_size_bytes\n              << \" bytes but only approximately \" << allocated_size_bytes\n              << \" bytes allocated via st_blocks. The file may be sparse or offloaded and \"\n                 \"open/read operations may fail.\"\n              << std::endl;\n}\n#else\nvoid warn_if_stat_size_and_blocks_differ_significantly(std::string const &) {}\n#endif\n}  // namespace\n\nFileReaderOptions::FileReaderOptions()\n: m_memory_pool(pod5::default_memory_pool())\n, m_max_cached_signal_table_batches(DEFAULT_MAX_CACHED_SIGNAL_TABLE_BATCHES)\n{\n}\n\nvoid FileReaderOptions::set_max_cached_signal_table_batches(\n    std::size_t max_cached_signal_table_batches)\n{\n    m_max_cached_signal_table_batches = max_cached_signal_table_batches;\n}\n\ninline FileLocation make_file_locaton(combined_file_utils::ParsedFileInfo const & parsed_file_info)\n{\n    return FileLocation{\n        parsed_file_info.file_path,\n        std::size_t(parsed_file_info.file_start_offset),\n        std::size_t(parsed_file_info.file_length)};\n}\n\nclass FileReaderImpl : public FileReader {\npublic:\n    FileReaderImpl(\n        Version const & file_version_pre_migration,\n        MigrationResult && migration_result,\n        RunInfoTableReader && run_info_table_reader,\n        ReadTableReader && read_table_reader,\n        SignalTableReader && signal_table_reader)\n    : m_file_version_pre_migration(file_version_pre_migration)\n    , m_migration_result(std::move(migration_result))\n    , m_run_info_table_location(make_file_locaton(m_migration_result.footer().run_info_table))\n    , m_read_table_location(make_file_locaton(m_migration_result.footer().reads_table))\n    , m_signal_table_location(make_file_locaton(m_migration_result.footer().signal_table))\n    , m_run_info_table_reader(std::move(run_info_table_reader))\n    , m_read_table_reader(std::move(read_table_reader))\n    , m_signal_table_reader(std::move(signal_table_reader))\n    {\n    }\n\n    SchemaMetadataDescription schema_metadata() const override\n    {\n        return m_read_table_reader.schema_metadata();\n    }\n\n    Result<std::size_t> run_info_count() const override\n    {\n        return m_run_info_table_reader.CountRows();\n    }\n\n    virtual Result<std::size_t> read_count() const override\n    {\n        auto const batch_count = num_read_record_batches();\n        if (batch_count == 0) {\n            return 0;\n        }\n\n        ARROW_ASSIGN_OR_RAISE(auto const first_batch, read_read_record_batch(0));\n        ARROW_ASSIGN_OR_RAISE(auto const last_batch, read_read_record_batch(batch_count - 1));\n\n        return (batch_count - 1) * first_batch.num_rows() + last_batch.num_rows();\n    }\n\n    Result<ReadTableRecordBatch> read_read_record_batch(std::size_t i) const override\n    {\n        return m_read_table_reader.read_record_batch(i);\n    }\n\n    std::size_t num_read_record_batches() const override\n    {\n        return m_read_table_reader.num_record_batches();\n    }\n\n    Result<std::size_t> search_for_read_ids(\n        ReadIdSearchInput const & search_input,\n        gsl::span<uint32_t> const & batch_counts,\n        gsl::span<uint32_t> const & batch_rows) const override\n    {\n        return m_read_table_reader.search_for_read_ids(search_input, batch_counts, batch_rows);\n    }\n\n    Result<SignalTableRecordBatch> read_signal_record_batch(std::size_t i) const override\n    {\n        return m_signal_table_reader.read_record_batch(i);\n    }\n\n    std::size_t num_signal_record_batches() const override\n    {\n        return m_signal_table_reader.num_record_batches();\n    }\n\n    Result<std::size_t> signal_batch_for_row_id(std::size_t row, std::size_t * batch_row)\n        const override\n    {\n        return m_signal_table_reader.signal_batch_for_row_id(row, batch_row);\n    }\n\n    Result<std::size_t> extract_sample_count(\n        gsl::span<std::uint64_t const> const & row_indices) const override\n    {\n        return m_signal_table_reader.extract_sample_count(row_indices);\n    }\n\n    Status extract_samples(\n        gsl::span<std::uint64_t const> const & row_indices,\n        gsl::span<std::int16_t> const & output_samples) const override\n    {\n        return m_signal_table_reader.extract_samples(row_indices, output_samples);\n    }\n\n    Result<std::vector<std::shared_ptr<arrow::Buffer>>> extract_samples_inplace(\n        gsl::span<std::uint64_t const> const & row_indices,\n        std::vector<std::uint32_t> & sample_count) const override\n    {\n        return m_signal_table_reader.extract_samples_inplace(row_indices, sample_count);\n    }\n\n    FileLocation const & run_info_table_location() const override\n    {\n        return m_run_info_table_location;\n    }\n\n    FileLocation const & read_table_location() const override { return m_read_table_location; }\n\n    FileLocation const & signal_table_location() const override { return m_signal_table_location; }\n\n    Version file_version_pre_migration() const override { return m_file_version_pre_migration; }\n\n    SignalType signal_type() const override { return m_signal_table_reader.signal_type(); }\n\n    Result<std::shared_ptr<RunInfoData const>> find_run_info(\n        std::string const & acquisition_id) const override\n    {\n        return m_run_info_table_reader.find_run_info(acquisition_id);\n    }\n\n    Result<std::shared_ptr<RunInfoData const>> get_run_info(std::size_t index) const override\n    {\n        return m_run_info_table_reader.get_run_info(index);\n    }\n\n    Result<std::size_t> get_run_info_count() const override\n    {\n        return m_run_info_table_reader.get_run_info_count();\n    }\n\nprivate:\n    Version m_file_version_pre_migration;\n    MigrationResult m_migration_result;\n    FileLocation m_run_info_table_location;\n    FileLocation m_read_table_location;\n    FileLocation m_signal_table_location;\n    RunInfoTableReader m_run_info_table_reader;\n    ReadTableReader m_read_table_reader;\n    SignalTableReader m_signal_table_reader;\n};\n\npod5::Result<std::shared_ptr<FileReader>> open_file_reader(\n    std::string const & path,\n    FileReaderOptions const & options)\n{\n    auto pool = options.memory_pool();\n    if (!pool) {\n        return Status::Invalid(\"Invalid memory pool specified for file writer\");\n    }\n\n    // Issue warning if the file appears to be a stub\n    warn_if_stat_size_and_blocks_differ_significantly(path);\n\n    // \"Preflight\" file reads are done via standard file I/O first to prevent SIGBUS errors\n    // if the file is not resident (e.g. stub remains when file is archived).\n    // If mmap succeeds afterwards, use it for normal table reads otherwise continue\n    // using this preflight file handle.\n    ARROW_ASSIGN_OR_RAISE(auto preflight_file, arrow::io::ReadableFile::Open(path, pool));\n    ARROW_ASSIGN_OR_RAISE(\n        auto original_footer_metadata, combined_file_utils::read_footer(path, preflight_file));\n\n    std::shared_ptr<arrow::io::RandomAccessFile> file = preflight_file;\n    if (!options.force_disable_file_mapping() && getenv(\"POD5_DISABLE_MMAP_OPEN\") == nullptr) {\n        auto file_opt = arrow::io::MemoryMappedFile::Open(path, arrow::io::FileMode::READ);\n        if (file_opt.ok()) {\n            file = *file_opt;\n            // Downstream handles are extracted from the `footer.{table}.file`, update them\n            // to use the mmap handle.\n            combined_file_utils::bind_footer_file(original_footer_metadata, file);\n        }\n    }\n\n    ARROW_ASSIGN_OR_RAISE(\n        auto const original_writer_version,\n        parse_version_number(original_footer_metadata.writer_pod5_version));\n    ARROW_ASSIGN_OR_RAISE(\n        auto migration_result,\n        migrate_if_required(original_writer_version, original_footer_metadata, file, pool));\n\n    // Files are written standalone, and so needs to be treated with a file offset - it wants to seek around as if the reads file is standalone:\n\n    ARROW_ASSIGN_OR_RAISE(\n        auto run_info_sub_file, open_sub_file(migration_result.footer().run_info_table));\n    ARROW_ASSIGN_OR_RAISE(\n        auto run_info_table_reader, make_run_info_table_reader(run_info_sub_file, pool));\n\n    ARROW_ASSIGN_OR_RAISE(\n        auto reads_sub_file, open_sub_file(migration_result.footer().reads_table));\n    ARROW_ASSIGN_OR_RAISE(auto read_table_reader, make_read_table_reader(reads_sub_file, pool));\n\n    ARROW_ASSIGN_OR_RAISE(\n        auto signal_sub_file, open_sub_file(migration_result.footer().signal_table));\n    ARROW_ASSIGN_OR_RAISE(\n        auto signal_table_reader,\n        make_signal_table_reader(signal_sub_file, options.max_cached_signal_table_batches(), pool));\n\n    auto signal_metadata = signal_table_reader.schema_metadata();\n    auto reads_metadata = read_table_reader.schema_metadata();\n    if (signal_metadata.file_identifier != reads_metadata.file_identifier) {\n        return Status::Invalid(\n            \"Invalid read and signal file pair signal identifier: \",\n            signal_metadata.file_identifier,\n            \", reads identifier: \",\n            reads_metadata.file_identifier);\n    }\n\n    return std::make_shared<FileReaderImpl>(\n        original_writer_version,\n        std::move(migration_result),\n        std::move(run_info_table_reader),\n        std::move(read_table_reader),\n        std::move(signal_table_reader));\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/file_reader.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/read_table_utils.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/signal_table_utils.h\"\n\n#include <cstdint>\n#include <memory>\n\nnamespace arrow {\nclass Array;\nclass Buffer;\nclass MemoryPool;\n}  // namespace arrow\n\nnamespace pod5 {\n\nclass Version;\nstruct SchemaMetadataDescription;\n\nclass POD5_FORMAT_EXPORT FileReaderOptions {\npublic:\n    static constexpr std::uint32_t DEFAULT_MAX_CACHED_SIGNAL_TABLE_BATCHES = 5;\n\n    FileReaderOptions();\n\n    void memory_pool(arrow::MemoryPool * memory_pool) { m_memory_pool = memory_pool; }\n\n    arrow::MemoryPool * memory_pool() const { return m_memory_pool; }\n\n    std::size_t max_cached_signal_table_batches() const\n    {\n        return m_max_cached_signal_table_batches;\n    }\n\n    // Set how many signal table batches can be cached in memory,\n    // Note: 0 here implies no limit.\n    void set_max_cached_signal_table_batches(std::size_t max_cached_signal_table_batches);\n\n    void set_force_disable_file_mapping(bool force_disable_file_mapping)\n    {\n        m_force_disable_file_mapping = force_disable_file_mapping;\n    }\n\n    bool force_disable_file_mapping() const { return m_force_disable_file_mapping; }\n\nprivate:\n    arrow::MemoryPool * m_memory_pool;\n    std::size_t m_max_cached_signal_table_batches;\n    bool m_force_disable_file_mapping = false;\n};\n\nclass POD5_FORMAT_EXPORT FileLocation {\npublic:\n    FileLocation(std::string const & file_path_, std::size_t offset_, std::size_t size_)\n    : file_path(file_path_)\n    , offset(offset_)\n    , size(size_)\n    {\n    }\n\n    std::string file_path;\n    std::size_t offset;\n    std::size_t size;\n};\n\nclass ReadTableRecordBatch;\nclass SignalTableRecordBatch;\n\nclass POD5_FORMAT_EXPORT FileReader {\npublic:\n    virtual ~FileReader() = default;\n\n    /// \\brief Find the read schema metadata for this file.\n    virtual SchemaMetadataDescription schema_metadata() const = 0;\n\n    virtual Result<std::size_t> run_info_count() const = 0;\n    virtual Result<std::size_t> read_count() const = 0;\n\n    virtual Result<ReadTableRecordBatch> read_read_record_batch(std::size_t i) const = 0;\n    virtual std::size_t num_read_record_batches() const = 0;\n\n    virtual Result<std::size_t> search_for_read_ids(\n        ReadIdSearchInput const & search_input,\n        gsl::span<uint32_t> const & batch_counts,\n        gsl::span<uint32_t> const & batch_rows) const = 0;\n\n    virtual Result<SignalTableRecordBatch> read_signal_record_batch(std::size_t i) const = 0;\n    virtual std::size_t num_signal_record_batches() const = 0;\n    virtual Result<std::size_t> signal_batch_for_row_id(std::size_t row, std::size_t * batch_row)\n        const = 0;\n    /// \\brief Find the number of samples in a given list of rows.\n    /// \\param row_indices      The rows to query for sample ount.\n    /// \\returns The sum of all sample counts on input rows.\n    virtual Result<std::size_t> extract_sample_count(\n        gsl::span<std::uint64_t const> const & row_indices) const = 0;\n\n    /// \\brief Extract the samples for a list of rows.\n    /// \\param row_indices      The rows to query for samples.\n    /// \\param output_samples   The output samples from the rows.\n    virtual Status extract_samples(\n        gsl::span<std::uint64_t const> const & row_indices,\n        gsl::span<std::int16_t> const & output_samples) const = 0;\n\n    /// \\brief Extract the samples as written in the arrow table for a list of rows.\n    /// \\param row_indices      The rows to query for samples.\n    /// \\param sample_count     The output samples from the rows.\n    virtual Result<std::vector<std::shared_ptr<arrow::Buffer>>> extract_samples_inplace(\n        gsl::span<std::uint64_t const> const & row_indices,\n        std::vector<std::uint32_t> & sample_count) const = 0;\n\n    virtual FileLocation const & run_info_table_location() const = 0;\n    virtual FileLocation const & read_table_location() const = 0;\n    virtual FileLocation const & signal_table_location() const = 0;\n\n    virtual Version file_version_pre_migration() const = 0;\n\n    virtual SignalType signal_type() const = 0;\n\n    virtual Result<std::shared_ptr<RunInfoData const>> find_run_info(\n        std::string const & acquisition_id) const = 0;\n\n    virtual Result<std::shared_ptr<RunInfoData const>> get_run_info(std::size_t index) const = 0;\n    virtual Result<std::size_t> get_run_info_count() const = 0;\n};\n\nPOD5_FORMAT_EXPORT pod5::Result<std::shared_ptr<FileReader>> open_file_reader(\n    std::string const & path,\n    FileReaderOptions const & options = {});\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/file_recovery.h",
    "content": "#pragma once\n\n#include \"pod5_format/internal/combined_file_utils.h\"\n#include \"pod5_format/schema_metadata.h\"\n\n#include <arrow/buffer.h>\n#include <arrow/io/file.h>\n#include <arrow/ipc/reader.h>\n#include <arrow/status.h>\n\n#include <iostream>\n\nnamespace pod5 {\n\nstatic constexpr char const * kArrowMagicBytes = \"ARROW1\";\n\nstruct RecoveredData {\n    // Metadata from the original file:\n    SchemaMetadataDescription metadata;\n\n    std::size_t recovered_batches = 0;\n    arrow::Status failed_batch_status;\n    std::size_t recovered_rows = 0;\n};\n\ntemplate <typename DestFileType>\narrow::Result<RecoveredData> recover_arrow_file(\n    std::shared_ptr<arrow::io::RandomAccessFile> const & file_to_recover,\n    DestFileType const & destination_file)\n{\n    // Check for arrow start file:\n    int32_t const magic_size = static_cast<int>(::strlen(kArrowMagicBytes));\n    ARROW_ASSIGN_OR_RAISE(auto buffer, file_to_recover->ReadAt(0, magic_size));\n    if (buffer->size() < magic_size || memcmp(buffer->data(), kArrowMagicBytes, magic_size)) {\n        return arrow::Status::Invalid(\"Not an Arrow file\");\n    }\n\n    // Open the stream format within the ipc file:\n    ARROW_ASSIGN_OR_RAISE(\n        auto input_stream, combined_file_utils::open_sub_file(file_to_recover, 8));\n    ARROW_ASSIGN_OR_RAISE(\n        auto opened_stream, arrow::ipc::RecordBatchStreamReader::Open(input_stream));\n\n    auto const & expected_schema = destination_file->schema();\n    auto schema = opened_stream->schema();\n    if (!schema->Equals(*expected_schema, false)) {\n        return arrow::Status::Invalid(\n            \"Recovered file Schema does not match expected schema, version mismatch?\");\n    }\n\n    RecoveredData recovered_data;\n    ARROW_ASSIGN_OR_RAISE(\n        recovered_data.metadata, read_schema_key_value_metadata(schema->metadata()));\n    while (true) {\n        auto result_opt = opened_stream->Next();\n        // Check if the batch failed to load:\n        if (!result_opt.ok()) {\n            recovered_data.failed_batch_status = result_opt.status();\n            return recovered_data;\n        }\n\n        auto & result = *result_opt;\n        if (!result) {\n            break;\n        }\n\n        recovered_data.recovered_batches += 1;\n        recovered_data.recovered_rows += result->num_rows();\n        ARROW_RETURN_NOT_OK(destination_file->write_batch(*result));\n    }\n\n    return recovered_data;\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/file_updater.cpp",
    "content": "#include \"pod5_format/file_updater.h\"\n\n#include \"pod5_format/file_reader.h\"\n#include \"pod5_format/internal/combined_file_utils.h\"\n#include \"pod5_format/schema_metadata.h\"\n#include \"pod5_format/uuid.h\"\n\n#include <arrow/io/file.h>\n\nnamespace pod5 {\n\npod5::Status update_file(\n    arrow::MemoryPool * pool,\n    std::shared_ptr<FileReader> const & source,\n    std::string destination)\n{\n    ARROW_ASSIGN_OR_RAISE(auto main_file, arrow::io::FileOutputStream::Open(destination, false));\n\n    std::random_device gen;\n    auto uuid_gen = BasicUuidRandomGenerator<std::random_device>{gen};\n    auto const section_marker = uuid_gen();\n\n    auto metadata = source->schema_metadata();\n\n    // Write the initial header to the combined file:\n    ARROW_RETURN_NOT_OK(combined_file_utils::write_combined_header(main_file, section_marker));\n\n    ARROW_ASSIGN_OR_RAISE(\n        auto signal_info_table,\n        combined_file_utils::write_file_and_marker(\n            pool,\n            main_file,\n            source->signal_table_location(),\n            combined_file_utils::SubFileCleanup::LeaveOrignalFile,\n            section_marker));\n    ARROW_ASSIGN_OR_RAISE(\n        auto run_info_info_table,\n        combined_file_utils::write_file_and_marker(\n            pool,\n            main_file,\n            source->run_info_table_location(),\n            combined_file_utils::SubFileCleanup::LeaveOrignalFile,\n            section_marker));\n    ARROW_ASSIGN_OR_RAISE(\n        auto reads_info_table,\n        combined_file_utils::write_file_and_marker(\n            pool,\n            main_file,\n            source->read_table_location(),\n            combined_file_utils::SubFileCleanup::LeaveOrignalFile,\n            section_marker));\n\n    // Write full file footer:\n    ARROW_RETURN_NOT_OK(\n        combined_file_utils::write_footer(\n            main_file,\n            section_marker,\n            metadata.file_identifier,\n            metadata.writing_software,\n            signal_info_table,\n            run_info_info_table,\n            reads_info_table));\n\n    return main_file->Close();\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/file_updater.h",
    "content": "#pragma once\n\n#include \"pod5_format/result.h\"\n\n#include <memory>\n\nnamespace arrow {\nclass MemoryPool;\n}\n\nnamespace pod5 {\n\nclass FileReader;\n\n/// \\brief Write the path [destination] with any migrated data from [source].\n/// \\param source The source file data to write updated.\n/// \\param destination The destination path to write the data to.\n/// \\note The destination path should not be the same file that was opened for input.\npod5::Status update_file(\n    arrow::MemoryPool * pool,\n    std::shared_ptr<FileReader> const & source,\n    std::string destination);\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/file_writer.cpp",
    "content": "#include \"pod5_format/file_writer.h\"\n\n#include \"pod5_format/file_recovery.h\"\n#include \"pod5_format/internal/async_output_stream.h\"\n#include \"pod5_format/internal/combined_file_utils.h\"\n#include \"pod5_format/io_manager.h\"\n#include \"pod5_format/memory_pool.h\"\n#include \"pod5_format/read_table_reader.h\"\n#include \"pod5_format/read_table_writer.h\"\n#include \"pod5_format/read_table_writer_utils.h\"\n#include \"pod5_format/run_info_table_writer.h\"\n#include \"pod5_format/schema_metadata.h\"\n#include \"pod5_format/signal_table_reader.h\"\n#include \"pod5_format/signal_table_writer.h\"\n#include \"pod5_format/thread_pool.h\"\n#include \"pod5_format/uuid.h\"\n#include \"pod5_format/version.h\"\n\n#include <arrow/io/file.h>\n#include <arrow/result.h>\n#include <arrow/util/future.h>\n#include <arrow/util/key_value_metadata.h>\n\n#include <filesystem>\n#include <fstream>\n#include <optional>\n\n#ifdef __linux__\n#include \"pod5_format/internal/linux_output_stream.h\"\n#endif\n\nnamespace {\nstruct CachedFileValues {\n    std::shared_ptr<pod5::IOManager> io_manager;\n    std::shared_ptr<pod5::ThreadPool> thread_pool;\n};\n\nenum class FlushMode { Default, ForceFlushOnBatchComplete };\n\narrow::Result<std::shared_ptr<pod5::FileOutputStream>> make_file_stream(\n    std::string const & path,\n    pod5::FileWriterOptions const & options,\n    CachedFileValues & cached_values,\n    bool keep_file_open,\n    FlushMode flush_mode = FlushMode::Default)\n{\n    auto const flush_on_batch_complete =\n        flush_mode == FlushMode::ForceFlushOnBatchComplete || options.flush_on_batch_complete();\n#ifdef __linux__\n    if (options.use_directio() || options.use_sync_io()) {\n        if (!cached_values.io_manager) {\n            if (options.io_manager()) {\n                cached_values.io_manager = options.io_manager();\n            } else {\n                ARROW_ASSIGN_OR_RAISE(\n                    cached_values.io_manager, pod5::make_sync_io_manager(options.memory_pool()));\n            }\n        }\n        auto const ret = pod5::LinuxOutputStream::make(\n            path,\n            cached_values.io_manager,\n            options.write_chunk_size(),\n            options.use_directio(),\n            options.use_sync_io(),\n            flush_on_batch_complete,\n            keep_file_open);\n\n        // Failure could be due to direct IO used by LinuxOutputStream not being\n        // supported. On error drop-through to make a regular AsyncOutputStream.\n        if (ret.ok()) {\n            return ret;\n        }\n    }\n\n#endif\n    if (!cached_values.thread_pool) {\n        if (options.thread_pool()) {\n            cached_values.thread_pool = options.thread_pool();\n        } else {\n            cached_values.thread_pool = pod5::make_thread_pool(1);\n        }\n    }\n\n    return pod5::AsyncOutputStream::make(\n        path,\n        cached_values.thread_pool,\n        flush_on_batch_complete,\n        options.memory_pool(),\n        keep_file_open);\n}\n}  // namespace\n\nnamespace pod5 {\n\nFileWriterOptions::FileWriterOptions()\n: m_max_signal_chunk_size(DEFAULT_SIGNAL_CHUNK_SIZE)\n, m_memory_pool(pod5::default_memory_pool())\n, m_signal_type(DEFAULT_SIGNAL_TYPE)\n, m_signal_table_batch_size(DEFAULT_SIGNAL_TABLE_BATCH_SIZE)\n, m_read_table_batch_size(DEFAULT_READ_TABLE_BATCH_SIZE)\n, m_run_info_table_batch_size(DEFAULT_RUN_INFO_TABLE_BATCH_SIZE)\n, m_use_directio{DEFAULT_USE_DIRECTIO}\n, m_write_chunk_size(DEFAULT_WRITE_CHUNK_SIZE)\n, m_use_sync_io(DEFAULT_USE_SYNC_IO)\n, m_flush_on_batch_complete(DEFAULT_FLUSH_ON_BATCH_COMPLETE)\n, m_keep_signal_file_open(DEFAULT_KEEP_FILES_OPEN)\n, m_keep_run_info_file_open(DEFAULT_KEEP_FILES_OPEN)\n, m_keep_read_table_file_open(DEFAULT_KEEP_FILES_OPEN)\n{\n}\n\nclass FileWriterImpl {\npublic:\n    class WriterTypeImpl;\n\n    struct DictionaryWriters {\n        std::shared_ptr<EndReasonWriter> end_reason_writer;\n        std::shared_ptr<PoreWriter> pore_writer;\n        std::shared_ptr<RunInfoWriter> run_info_writer;\n    };\n\n    FileWriterImpl(\n        DictionaryWriters && read_table_dict_writers,\n        RunInfoTableWriter && run_info_table_writer,\n        ReadTableWriter && read_table_writer,\n        SignalTableWriter && signal_table_writer,\n        std::uint32_t signal_chunk_size,\n        arrow::MemoryPool * pool)\n    : m_read_table_dict_writers(std::move(read_table_dict_writers))\n    , m_run_info_table_writer(std::move(run_info_table_writer))\n    , m_read_table_writer(std::move(read_table_writer))\n    , m_signal_table_writer(std::move(signal_table_writer))\n    , m_signal_chunk_size(signal_chunk_size)\n    , m_pool(pool)\n    {\n    }\n\n    virtual ~FileWriterImpl() = default;\n\n    virtual std::string path() const = 0;\n\n    pod5::Result<EndReasonDictionaryIndex> lookup_end_reason(ReadEndReason end_reason)\n    {\n        return m_read_table_dict_writers.end_reason_writer->lookup(end_reason);\n    }\n\n    pod5::Result<PoreDictionaryIndex> add_pore_type(std::string const & pore_type_data)\n    {\n        return m_read_table_dict_writers.pore_writer->add(pore_type_data);\n    }\n\n    pod5::Result<RunInfoDictionaryIndex> add_run_info(RunInfoData const & run_info_data)\n    {\n        ARROW_RETURN_NOT_OK(m_run_info_table_writer->add_run_info(run_info_data));\n        return m_read_table_dict_writers.run_info_writer->add(run_info_data.acquisition_id);\n    }\n\n    pod5::Status add_complete_read(\n        ReadData const & read_data,\n        gsl::span<std::int16_t const> const & signal)\n    {\n        if (!m_signal_table_writer || !m_read_table_writer) {\n            return arrow::Status::Invalid(\"File writer closed, cannot write further data\");\n        }\n\n        ARROW_RETURN_NOT_OK(check_read(read_data));\n\n        ARROW_ASSIGN_OR_RAISE(\n            std::vector<std::uint64_t> signal_rows, add_signal(read_data.read_id, signal));\n\n        // Write read data and signal row entries:\n        auto read_table_row = m_read_table_writer->add_read(\n            read_data, gsl::make_span(signal_rows.data(), signal_rows.size()), signal.size());\n        return read_table_row.status();\n    }\n\n    pod5::Status add_complete_read(\n        ReadData const & read_data,\n        gsl::span<std::uint64_t const> const & signal_rows,\n        std::uint64_t signal_duration)\n    {\n        if (!m_signal_table_writer || !m_read_table_writer) {\n            return arrow::Status::Invalid(\"File writer closed, cannot write further data\");\n        }\n\n        ARROW_RETURN_NOT_OK(check_read(read_data));\n\n        // Write read data and signal row entries:\n        auto read_table_row =\n            m_read_table_writer->add_read(read_data, signal_rows, signal_duration);\n        return read_table_row.status();\n    }\n\n    arrow::Status check_read(ReadData const & read_data)\n    {\n        if (!m_read_table_dict_writers.run_info_writer->is_valid(read_data.run_info)) {\n            return arrow::Status::Invalid(\"Invalid run info passed to add_read\");\n        }\n\n        if (!m_read_table_dict_writers.pore_writer->is_valid(read_data.pore_type)) {\n            return arrow::Status::Invalid(\"Invalid pore type passed to add_read\");\n        }\n\n        if (!m_read_table_dict_writers.end_reason_writer->is_valid(read_data.end_reason)) {\n            return arrow::Status::Invalid(\"Invalid end reason passed to add_read\");\n        }\n\n        return arrow::Status::OK();\n    }\n\n    pod5::Result<std::vector<SignalTableRowIndex>> add_signal(\n        Uuid const & read_id,\n        gsl::span<std::int16_t const> const & signal)\n    {\n        if (!m_signal_table_writer || !m_read_table_writer) {\n            return arrow::Status::Invalid(\"File writer closed, cannot write further data\");\n        }\n\n        std::vector<SignalTableRowIndex> signal_rows;\n        signal_rows.reserve((signal.size() / m_signal_chunk_size) + 1);\n\n        // Chunk and write each piece of signal to the file:\n        for (std::size_t chunk_start = 0; chunk_start < signal.size();\n             chunk_start += m_signal_chunk_size)\n        {\n            std::size_t chunk_size =\n                std::min<std::size_t>(signal.size() - chunk_start, m_signal_chunk_size);\n\n            auto const chunk_span = signal.subspan(chunk_start, chunk_size);\n\n            ARROW_ASSIGN_OR_RAISE(\n                auto row_index, m_signal_table_writer->add_signal(read_id, chunk_span));\n            signal_rows.push_back(row_index);\n        }\n        return signal_rows;\n    }\n\n    pod5::Result<SignalTableRowIndex> add_pre_compressed_signal(\n        Uuid const & read_id,\n        gsl::span<std::uint8_t const> const & signal_bytes,\n        std::uint32_t sample_count)\n    {\n        if (!m_signal_table_writer || !m_read_table_writer) {\n            return arrow::Status::Invalid(\"File writer closed, cannot write further data\");\n        }\n\n        return m_signal_table_writer->add_pre_compressed_signal(\n            read_id, signal_bytes, sample_count);\n    }\n\n    pod5::Result<std::pair<SignalTableRowIndex, SignalTableRowIndex>> add_signal_batch(\n        std::size_t row_count,\n        std::vector<std::shared_ptr<arrow::Array>> && columns,\n        bool final_batch)\n    {\n        if (!m_signal_table_writer || !m_read_table_writer) {\n            return arrow::Status::Invalid(\"File writer closed, cannot write further data\");\n        }\n\n        return m_signal_table_writer->add_signal_batch(row_count, std::move(columns), final_batch);\n    }\n\n    SignalType signal_type() const { return m_signal_table_writer->signal_type(); }\n\n    std::size_t signal_table_batch_size() const\n    {\n        return m_signal_table_writer->table_batch_size();\n    }\n\n    pod5::Status close_run_info_table_writer()\n    {\n        if (m_run_info_table_writer) {\n            ARROW_RETURN_NOT_OK(m_run_info_table_writer->close());\n            m_run_info_table_writer = std::nullopt;\n        }\n        return pod5::Status::OK();\n    }\n\n    pod5::Status close_read_table_writer()\n    {\n        if (m_read_table_writer) {\n            ARROW_RETURN_NOT_OK(m_read_table_writer->close());\n            m_read_table_writer = std::nullopt;\n        }\n        return pod5::Status::OK();\n    }\n\n    pod5::Status close_signal_table_writer()\n    {\n        if (m_signal_table_writer) {\n            ARROW_RETURN_NOT_OK(m_signal_table_writer->close());\n            m_signal_table_writer = std::nullopt;\n        }\n        return pod5::Status::OK();\n    }\n\n    virtual arrow::Status close() = 0;\n\n    bool is_closed() const\n    {\n        assert(!!m_read_table_writer == !!m_signal_table_writer);\n        return !m_signal_table_writer;\n    }\n\n    arrow::MemoryPool * pool() const { return m_pool; }\n\n    RunInfoTableWriter * run_info_table_writer()\n    {\n        if (is_closed() || !m_run_info_table_writer.has_value()) {\n            return nullptr;\n        }\n        return &m_run_info_table_writer.value();\n    }\n\n    ReadTableWriter * read_table_writer()\n    {\n        if (is_closed() || !m_read_table_writer.has_value()) {\n            return nullptr;\n        }\n        return &m_read_table_writer.value();\n    }\n\n    SignalTableWriter * signal_table_writer()\n    {\n        if (is_closed() || !m_signal_table_writer.has_value()) {\n            return nullptr;\n        }\n        return &m_signal_table_writer.value();\n    }\n\nprivate:\n    DictionaryWriters m_read_table_dict_writers;\n    std::optional<RunInfoTableWriter> m_run_info_table_writer;\n    std::optional<ReadTableWriter> m_read_table_writer;\n    std::optional<SignalTableWriter> m_signal_table_writer;\n    std::uint32_t m_signal_chunk_size;\n    arrow::MemoryPool * m_pool;\n};\n\nclass CombinedFileWriterImpl : public FileWriterImpl {\npublic:\n    CombinedFileWriterImpl(\n        std::string const & path,\n        std::string const & run_info_tmp_path,\n        std::string const & reads_tmp_path,\n        std::int64_t signal_file_start_offset,\n        Uuid const & section_marker,\n        Uuid const & file_identifier,\n        std::string const & software_name,\n        DictionaryWriters && dict_writers,\n        RunInfoTableWriter && run_info_table_writer,\n        ReadTableWriter && read_table_writer,\n        SignalTableWriter && signal_table_writer,\n        std::uint32_t signal_chunk_size,\n        arrow::MemoryPool * pool)\n    : FileWriterImpl(\n          std::move(dict_writers),\n          std::move(run_info_table_writer),\n          std::move(read_table_writer),\n          std::move(signal_table_writer),\n          signal_chunk_size,\n          pool)\n    , m_path(path)\n    , m_run_info_tmp_path(run_info_tmp_path)\n    , m_reads_tmp_path(reads_tmp_path)\n    , m_signal_file_start_offset(signal_file_start_offset)\n    , m_section_marker(section_marker)\n    , m_file_identifier(file_identifier)\n    , m_software_name(software_name)\n    {\n    }\n\n    std::string path() const override { return m_path; }\n\n    arrow::Status close() override\n    {\n        if (is_closed()) {\n            return arrow::Status::OK();\n        }\n        ARROW_RETURN_NOT_OK(close_run_info_table_writer());\n        ARROW_RETURN_NOT_OK(close_read_table_writer());\n        ARROW_RETURN_NOT_OK(close_signal_table_writer());\n\n        // Open main path with append set:\n        ARROW_ASSIGN_OR_RAISE(auto file, arrow::io::FileOutputStream::Open(m_path, true));\n\n        // Record signal table length:\n        combined_file_utils::FileInfo signal_table;\n        signal_table.file_start_offset = m_signal_file_start_offset;\n        ARROW_ASSIGN_OR_RAISE(signal_table.file_length, file->Tell());\n        signal_table.file_length -= signal_table.file_start_offset;\n\n        // pad file to 8 bytes and mark section:\n        ARROW_RETURN_NOT_OK(combined_file_utils::pad_file(file, 8));\n        ARROW_RETURN_NOT_OK(combined_file_utils::write_section_marker(file, m_section_marker));\n\n        auto file_location_for_full_file =\n            [&](std::string const & filename) -> arrow::Result<FileLocation> {\n            ARROW_ASSIGN_OR_RAISE(auto file, arrow::io::ReadableFile::Open(filename, pool()));\n            ARROW_ASSIGN_OR_RAISE(auto size, file->GetSize());\n            return FileLocation{filename, 0, std::size_t(size)};\n        };\n\n        // Write in run_info table:\n        ARROW_ASSIGN_OR_RAISE(\n            auto run_info_location, file_location_for_full_file(m_run_info_tmp_path));\n        ARROW_ASSIGN_OR_RAISE(\n            auto run_info_info_table,\n            combined_file_utils::write_file_and_marker(\n                pool(),\n                file,\n                run_info_location,\n                combined_file_utils::SubFileCleanup::CleanupOriginalFile,\n                m_section_marker));\n\n        // Write in read table:\n        ARROW_ASSIGN_OR_RAISE(auto reads_location, file_location_for_full_file(m_reads_tmp_path));\n        ARROW_ASSIGN_OR_RAISE(\n            auto reads_info_table,\n            combined_file_utils::write_file_and_marker(\n                pool(),\n                file,\n                reads_location,\n                combined_file_utils::SubFileCleanup::CleanupOriginalFile,\n                m_section_marker));\n\n        // Write full file footer:\n        ARROW_RETURN_NOT_OK(\n            combined_file_utils::write_footer(\n                file,\n                m_section_marker,\n                m_file_identifier,\n                m_software_name,\n                signal_table,\n                run_info_info_table,\n                reads_info_table));\n        return arrow::Status::OK();\n    }\n\nprivate:\n    std::string m_path;\n    std::string m_run_info_tmp_path;\n    std::string m_reads_tmp_path;\n    std::int64_t m_signal_file_start_offset;\n    Uuid m_section_marker;\n    Uuid m_file_identifier;\n    std::string m_software_name;\n};\n\nFileWriter::FileWriter(std::unique_ptr<FileWriterImpl> && impl) : m_impl(std::move(impl)) {}\n\nFileWriter::~FileWriter() { (void)close(); }\n\nstd::string FileWriter::path() const { return m_impl->path(); }\n\narrow::Status FileWriter::close() { return m_impl->close(); }\n\narrow::Status FileWriter::add_complete_read(\n    ReadData const & read_data,\n    gsl::span<std::int16_t const> const & signal)\n{\n    return m_impl->add_complete_read(read_data, signal);\n}\n\narrow::Status FileWriter::add_complete_read(\n    ReadData const & read_data,\n    gsl::span<std::uint64_t const> const & signal_rows,\n    std::uint64_t signal_duration)\n{\n    return m_impl->add_complete_read(read_data, signal_rows, signal_duration);\n}\n\npod5::Result<std::vector<SignalTableRowIndex>> FileWriter::add_signal(\n    Uuid const & read_id,\n    gsl::span<std::int16_t const> const & signal)\n{\n    return m_impl->add_signal(read_id, signal);\n}\n\npod5::Result<SignalTableRowIndex> FileWriter::add_pre_compressed_signal(\n    Uuid const & read_id,\n    gsl::span<std::uint8_t const> const & signal_bytes,\n    std::uint32_t sample_count)\n{\n    return m_impl->add_pre_compressed_signal(read_id, signal_bytes, sample_count);\n}\n\npod5::Result<std::pair<SignalTableRowIndex, SignalTableRowIndex>> FileWriter::add_signal_batch(\n    std::size_t row_count,\n    std::vector<std::shared_ptr<arrow::Array>> && columns,\n    bool final_batch)\n{\n    return m_impl->add_signal_batch(row_count, std::move(columns), final_batch);\n}\n\npod5::Result<EndReasonDictionaryIndex> FileWriter::lookup_end_reason(ReadEndReason end_reason) const\n{\n    return m_impl->lookup_end_reason(end_reason);\n}\n\npod5::Result<PoreDictionaryIndex> FileWriter::add_pore_type(std::string const & pore_type_data)\n{\n    return m_impl->add_pore_type(pore_type_data);\n}\n\npod5::Result<RunInfoDictionaryIndex> FileWriter::add_run_info(RunInfoData const & run_info_data)\n{\n    return m_impl->add_run_info(run_info_data);\n}\n\nSignalType FileWriter::signal_type() const { return m_impl->signal_type(); }\n\nstd::size_t FileWriter::signal_table_batch_size() const\n{\n    return m_impl->signal_table_batch_size();\n}\n\npod5::Result<FileWriterImpl::DictionaryWriters> make_dictionary_writers(arrow::MemoryPool * pool)\n{\n    FileWriterImpl::DictionaryWriters writers;\n\n    ARROW_ASSIGN_OR_RAISE(writers.end_reason_writer, pod5::make_end_reason_writer(pool));\n    ARROW_ASSIGN_OR_RAISE(writers.pore_writer, pod5::make_pore_writer(pool));\n    ARROW_ASSIGN_OR_RAISE(writers.run_info_writer, pod5::make_run_info_writer(pool));\n\n    return writers;\n}\n\nstd::string make_reads_tmp_path(\n    ::arrow::internal::PlatformFilename const & arrow_path,\n    Uuid const & file_identifier)\n{\n    return arrow_path.Parent().ToString() + \"/\" + (\".\" + to_string(file_identifier) + \".tmp-reads\");\n}\n\nstd::string make_run_info_tmp_path(\n    ::arrow::internal::PlatformFilename const & arrow_path,\n    Uuid const & file_identifier)\n{\n    return arrow_path.Parent().ToString() + \"/\"\n           + (\".\" + to_string(file_identifier) + \".tmp-run-info\");\n}\n\npod5::Result<std::unique_ptr<FileWriter>> create_file_writer(\n    std::string const & path,\n    std::string const & writing_software_name,\n    FileWriterOptions const & options)\n{\n    auto pool = options.memory_pool();\n    if (!pool) {\n        return Status::Invalid(\"Invalid memory pool specified for file writer\");\n    }\n\n    ARROW_ASSIGN_OR_RAISE(auto arrow_path, ::arrow::internal::PlatformFilename::FromString(path));\n    ARROW_ASSIGN_OR_RAISE(bool file_exists, arrow::internal::FileExists(arrow_path));\n    if (file_exists) {\n        return Status::Invalid(\"Unable to create new file '\", path, \"', already exists\");\n    }\n\n    // Open dictionary writers:\n    ARROW_ASSIGN_OR_RAISE(auto dict_writers, make_dictionary_writers(pool));\n\n    // Prep file metadata:\n    std::random_device gen;\n    auto uuid_gen = BasicUuidRandomGenerator<std::random_device>{gen};\n    auto const section_marker = uuid_gen();\n    auto const file_identifier = uuid_gen();\n\n    ARROW_ASSIGN_OR_RAISE(auto current_version, parse_version_number(Pod5Version));\n    ARROW_ASSIGN_OR_RAISE(\n        auto file_schema_metadata,\n        make_schema_key_value_metadata({file_identifier, writing_software_name, current_version}));\n\n    auto reads_tmp_path = make_reads_tmp_path(arrow_path, file_identifier);\n    auto run_info_tmp_path = make_run_info_tmp_path(arrow_path, file_identifier);\n\n    CachedFileValues cached_values;\n\n    // Prepare the temporary reads file:\n    ARROW_ASSIGN_OR_RAISE(\n        auto read_table_file_async,\n        make_file_stream(\n            reads_tmp_path, options, cached_values, options.keep_read_table_file_open()));\n    ARROW_ASSIGN_OR_RAISE(\n        auto read_table_tmp_writer,\n        make_read_table_writer(\n            read_table_file_async,\n            file_schema_metadata,\n            options.read_table_batch_size(),\n            dict_writers.pore_writer,\n            dict_writers.end_reason_writer,\n            dict_writers.run_info_writer,\n            pool));\n\n    // Prepare the temporary run_info file:\n    //\n    // Run info is normally global, if we don't flush on batch complete we can\n    // lose a large number of reads in a crash.\n    ARROW_ASSIGN_OR_RAISE(\n        auto run_info_table_file_async,\n        make_file_stream(\n            run_info_tmp_path,\n            options,\n            cached_values,\n            options.keep_run_info_file_open(),\n            FlushMode::ForceFlushOnBatchComplete));\n\n    ARROW_ASSIGN_OR_RAISE(\n        auto run_info_table_tmp_writer,\n        make_run_info_table_writer(\n            run_info_table_file_async,\n            file_schema_metadata,\n            options.run_info_table_batch_size(),\n            pool));\n\n    // Prepare the main file - and set up the signal table to write here:\n    ARROW_ASSIGN_OR_RAISE(\n        auto signal_file,\n        make_file_stream(path, options, cached_values, options.keep_signal_file_open()));\n\n    // Write the initial header to the combined file:\n    ARROW_RETURN_NOT_OK(combined_file_utils::write_combined_header(signal_file, section_marker));\n\n    ARROW_ASSIGN_OR_RAISE(size_t const signal_table_start, signal_file->Tell());\n\n    static_cast<AsyncOutputStream *>(signal_file.get())->set_file_start_offset(signal_table_start);\n\n    // Then place the signal file directly after that:\n    ARROW_ASSIGN_OR_RAISE(\n        auto signal_table_writer,\n        make_signal_table_writer(\n            signal_file,\n            file_schema_metadata,\n            options.signal_table_batch_size(),\n            options.signal_type(),\n            pool));\n\n    // Throw it all together into a writer object:\n    return std::make_unique<FileWriter>(std::make_unique<CombinedFileWriterImpl>(\n        path,\n        run_info_tmp_path,\n        reads_tmp_path,\n        signal_table_start,\n        section_marker,\n        file_identifier,\n        writing_software_name,\n        std::move(dict_writers),\n        std::move(run_info_table_tmp_writer),\n        std::move(read_table_tmp_writer),\n        std::move(signal_table_writer),\n        options.max_signal_chunk_size(),\n        pool));\n}\n\nstatic Status add_recovery_failure_context(\n    Status status,\n    std::string const & tmp_path,\n    std::string const & description)\n{\n    assert(!status.ok());\n    std::string const error_context =\n        \"Failed whilst attempting to recover \" + description + \" from file - \" + tmp_path;\n    if (status.detail()) {\n        return status.WithMessage(error_context);\n    }\n    return arrow::Status::FromArgs(status.code(), error_context + \". Detail: \" + status.message());\n}\n\ntemplate <typename writer_type>\nstatic Status append_recovered_file(\n    std::string const & tmp_path,\n    writer_type const & destination_writer,\n    std::string const & description,\n    arrow::MemoryPool * const pool)\n{\n    arrow::Status inner_status = [&] {\n        ARROW_ASSIGN_OR_RAISE(auto file, arrow::io::ReadableFile::Open(tmp_path, pool));\n        ARROW_ASSIGN_OR_RAISE(auto size, file->GetSize());\n        if (size == 0) {\n            return arrow::Status::Invalid(\"File is empty/zero bytes long.\");\n        }\n        ARROW_ASSIGN_OR_RAISE(\n            RecoveredData const recovered_raw_data, recover_arrow_file(file, destination_writer));\n        return arrow::Status::OK();\n    }();\n    if (!inner_status.ok()) {\n        return add_recovery_failure_context(inner_status, tmp_path, description);\n    }\n    return inner_status;\n}\n\nnamespace {\nstruct TemporaryFilePaths {\n    std::string run_info;\n    std::string reads;\n};\n}  // namespace\n\nstatic pod5::Status recover_file(\n    std::string const & src_path,\n    std::string const & dest_path,\n    FileWriterOptions const & options,\n    TemporaryFilePaths & temporary_file_paths)\n{\n    if (!check_extension_types_registered()) {\n        return arrow::Status::Invalid(\"POD5 library is not correctly initialised.\");\n    }\n\n    // Create a file to push recovered data into:\n    ARROW_ASSIGN_OR_RAISE(\n        auto dest_file, create_file_writer(dest_path, \"pod5_file_recovery\", options));\n\n    auto pool = arrow::default_memory_pool();\n    ARROW_ASSIGN_OR_RAISE(\n        auto arrow_path, ::arrow::internal::PlatformFilename::FromString(src_path));\n    ARROW_ASSIGN_OR_RAISE(auto file, arrow::io::ReadableFile::Open(src_path, pool));\n\n    // Signature should be right at 0:\n    ARROW_RETURN_NOT_OK(combined_file_utils::check_signature(file, 0));\n\n    // Recover the signal data into [dest_file]:\n    arrow::Result<RecoveredData> recovered_raw_data;\n    {\n        ARROW_ASSIGN_OR_RAISE(\n            auto raw_sub_file,\n            combined_file_utils::open_sub_file(file, combined_file_utils::header_size));\n        recovered_raw_data =\n            recover_arrow_file(raw_sub_file, dest_file->impl()->signal_table_writer());\n    }\n    if (!recovered_raw_data.ok()) {\n        return add_recovery_failure_context(\n            recovered_raw_data.status(), arrow_path.ToString(), \"signal data sub file\");\n    }\n\n    auto file_identifier = recovered_raw_data->metadata.file_identifier;\n    temporary_file_paths.run_info = make_run_info_tmp_path(arrow_path, file_identifier);\n    temporary_file_paths.reads = make_reads_tmp_path(arrow_path, file_identifier);\n\n    // Recover the run info data into [dest_file]:\n    auto run_info_writer = dest_file->impl()->run_info_table_writer();\n    ARROW_RETURN_NOT_OK(append_recovered_file(\n        temporary_file_paths.run_info, run_info_writer, \"run information\", pool));\n\n    // Recover the read data into [dest_file]:\n    auto read_writer = dest_file->impl()->read_table_writer();\n    ARROW_RETURN_NOT_OK(\n        append_recovered_file(temporary_file_paths.reads, read_writer, \"reads\", pool));\n\n    return dest_file->close();\n}\n\n/// This is a thorough count of all rows. Doing it this way ensures that all rows can be read.\nstatic pod5::Result<RecoveredRowCounts> count_recovered_rows(\n    std::filesystem::path const & recovered_path)\n{\n    ARROW_ASSIGN_OR_RAISE(\n        std::shared_ptr<pod5::FileReader> recovered,\n        pod5::open_file_reader(recovered_path.string()));\n    RecoveredRowCounts counts;\n\n    std::size_t const signal_batches = recovered->num_signal_record_batches();\n    for (std::size_t index = 0; index < signal_batches; ++index) {\n        ARROW_ASSIGN_OR_RAISE(auto const signal_batch, recovered->read_signal_record_batch(index));\n        counts.signal += signal_batch.num_rows();\n    }\n\n    ARROW_ASSIGN_OR_RAISE(counts.run_info, recovered->run_info_count());\n\n    std::size_t const read_batches = recovered->num_read_record_batches();\n    for (std::size_t index = 0; index < read_batches; ++index) {\n        ARROW_ASSIGN_OR_RAISE(auto const record_batch, recovered->read_read_record_batch(index));\n        counts.reads += record_batch.num_rows();\n    }\n    return counts;\n}\n\n/// \\brief File is considered useless for recovery if it is 0 bytes long\n/// or if all bytes have value 0.\nstatic bool is_useless(std::filesystem::path const & file_path)\n{\n    if (file_size(file_path) == 0) {\n        return true;\n    }\n    std::ifstream file{file_path, std::ios::in | std::ios::binary};\n    if (!file.is_open()) {\n        // If we can't open the file, assume there is data, just in case.\n        return false;\n    }\n    while (true) {\n        std::uint8_t byte;\n        file >> byte;\n        if (file.eof()) {\n            return true;\n        }\n        if (byte != 0) {\n            return false;\n        }\n    }\n}\n\nstatic void remove_if_useless(std::filesystem::path const & file_path)\n{\n    if (exists(file_path) && is_useless(file_path)) {\n        remove(file_path);\n    }\n}\n\nstatic std::optional<CleanupError> try_remove(std::string const & file_path)\n{\n    try {\n        std::filesystem::remove(file_path);\n        return {};\n    } catch (std::filesystem::filesystem_error const & error) {\n        return CleanupError{.file_path = file_path, .description = error.what()};\n    }\n}\n\nstatic Status add_clean_up_error(Status status, std::filesystem::filesystem_error const & exception)\n{\n    return arrow::Status::FromArgs(status.code(), status.message(), exception.what());\n}\n\npod5::Result<RecoveryDetails> recover_file(\n    std::string const & src_path,\n    std::string const & dest_path,\n    RecoverFileOptions const & options)\n{\n    TemporaryFilePaths temp_file_paths;\n    auto const result = [&]() -> pod5::Result<RecoveredRowCounts> {\n        ARROW_RETURN_NOT_OK(\n            recover_file(src_path, dest_path, options.file_writer_options, temp_file_paths));\n        auto const row_count_result = count_recovered_rows(dest_path);\n        if (!row_count_result.ok()) {\n            return add_recovery_failure_context(row_count_result.status(), dest_path, \"row counts\");\n        }\n        return row_count_result;\n    }();\n    if (!options.cleanup) {\n        auto const to_recovery_details = [](RecoveredRowCounts counts) {\n            return RecoveryDetails{counts};\n        };\n        return result.Map(to_recovery_details);\n    }\n    if (!result.ok()) {\n        try {\n            if (std::filesystem::exists(dest_path)) {\n                std::filesystem::remove(dest_path);\n            }\n            remove_if_useless(temp_file_paths.reads);\n            remove_if_useless(temp_file_paths.run_info);\n            remove_if_useless(src_path);\n        } catch (std::filesystem::filesystem_error const & error) {\n            return add_clean_up_error(result.status(), error);\n        }\n        return result.status();\n    }\n\n    RecoveryDetails details{.row_counts = *result};\n    if (auto const error = try_remove(src_path)) {\n        details.cleanup_errors.push_back(*error);\n    }\n    if (auto const error = try_remove(temp_file_paths.reads)) {\n        details.cleanup_errors.push_back(*error);\n    }\n    if (auto const error = try_remove(temp_file_paths.run_info)) {\n        details.cleanup_errors.push_back(*error);\n    }\n    return details;\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/file_writer.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/read_table_utils.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/signal_table_utils.h\"\n\n#include <cstdint>\n#include <memory>\n\nnamespace arrow {\nclass Array;\nclass MemoryPool;\n}  // namespace arrow\n\nnamespace pod5 {\n\nclass IOManager;\nclass ThreadPool;\n\nclass POD5_FORMAT_EXPORT FileWriterOptions {\npublic:\n    /// \\brief Default chunk size for signal table entries\n    static constexpr std::uint32_t DEFAULT_SIGNAL_CHUNK_SIZE = 102'400;\n    static constexpr std::uint32_t DEFAULT_SIGNAL_TABLE_BATCH_SIZE = 100;\n    static constexpr std::uint32_t DEFAULT_READ_TABLE_BATCH_SIZE = 1000;\n    static constexpr std::uint32_t DEFAULT_RUN_INFO_TABLE_BATCH_SIZE = 1;\n    static constexpr SignalType DEFAULT_SIGNAL_TYPE = SignalType::VbzSignal;\n    static constexpr bool DEFAULT_USE_DIRECTIO = false;\n    static constexpr bool DEFAULT_USE_SYNC_IO = false;\n    static constexpr bool DEFAULT_FLUSH_ON_BATCH_COMPLETE = true;\n    static constexpr std::size_t DEFAULT_WRITE_CHUNK_SIZE = 2 * 1024 * 1024;\n    static constexpr std::size_t DEFAULT_KEEP_FILES_OPEN = true;\n\n    FileWriterOptions();\n\n    void set_max_signal_chunk_size(std::uint32_t chunk_size)\n    {\n        m_max_signal_chunk_size = chunk_size;\n    }\n\n    std::uint32_t max_signal_chunk_size() const { return m_max_signal_chunk_size; }\n\n    void set_memory_pool(arrow::MemoryPool * memory_pool) { m_memory_pool = memory_pool; }\n\n    arrow::MemoryPool * memory_pool() const { return m_memory_pool; }\n\n    void set_signal_type(SignalType signal_type) { m_signal_type = signal_type; }\n\n    SignalType signal_type() const { return m_signal_type; }\n\n    void set_signal_table_batch_size(std::size_t batch_size)\n    {\n        m_signal_table_batch_size = batch_size;\n    }\n\n    std::size_t signal_table_batch_size() const { return m_signal_table_batch_size; }\n\n    void set_read_table_batch_size(std::size_t batch_size) { m_read_table_batch_size = batch_size; }\n\n    std::size_t read_table_batch_size() const { return m_read_table_batch_size; }\n\n    void set_run_info_table_batch_size(std::size_t batch_size)\n    {\n        m_run_info_table_batch_size = batch_size;\n    }\n\n    std::size_t run_info_table_batch_size() const { return m_run_info_table_batch_size; }\n\n    void set_io_manager(std::shared_ptr<IOManager> const & io_manager)\n    {\n        m_io_manager = io_manager;\n    }\n\n    std::shared_ptr<IOManager> io_manager() const { return m_io_manager; }\n\n    void set_thread_pool(std::shared_ptr<ThreadPool> const & writer_thread_pool)\n    {\n        m_writer_thread_pool = writer_thread_pool;\n    }\n\n    std::shared_ptr<ThreadPool> thread_pool() const { return m_writer_thread_pool; }\n\n    void set_use_directio(bool use_directio) { m_use_directio = use_directio; }\n\n    bool use_directio() const { return m_use_directio; }\n\n    void set_write_chunk_size(std::size_t chunk_size) { m_write_chunk_size = chunk_size; }\n\n    std::size_t write_chunk_size() const { return m_write_chunk_size; }\n\n    void set_use_sync_io(bool use_sync_io) { m_use_sync_io = use_sync_io; }\n\n    bool use_sync_io() const { return m_use_sync_io; }\n\n    void set_flush_on_batch_complete(bool flush_on_batch_complete)\n    {\n        m_flush_on_batch_complete = flush_on_batch_complete;\n    }\n\n    bool flush_on_batch_complete() const { return m_flush_on_batch_complete; }\n\n    bool keep_signal_file_open() const { return m_keep_signal_file_open; }\n\n    void set_keep_signal_file_open(bool keep_signal_file_open)\n    {\n        m_keep_signal_file_open = keep_signal_file_open;\n    }\n\n    bool keep_run_info_file_open() const { return m_keep_run_info_file_open; }\n\n    void set_keep_run_info_file_open(bool keep_run_info_file_open)\n    {\n        m_keep_run_info_file_open = keep_run_info_file_open;\n    }\n\n    bool keep_read_table_file_open() const { return m_keep_read_table_file_open; }\n\n    void set_keep_read_table_file_open(bool keep_read_table_file_open)\n    {\n        m_keep_read_table_file_open = keep_read_table_file_open;\n    }\n\nprivate:\n    std::shared_ptr<ThreadPool> m_writer_thread_pool;\n    std::shared_ptr<IOManager> m_io_manager;\n    std::uint32_t m_max_signal_chunk_size;\n    arrow::MemoryPool * m_memory_pool;\n    SignalType m_signal_type;\n    std::size_t m_signal_table_batch_size;\n    std::size_t m_read_table_batch_size;\n    std::size_t m_run_info_table_batch_size;\n    bool m_use_directio;\n    std::size_t m_write_chunk_size;\n    bool m_use_sync_io;\n    bool m_flush_on_batch_complete;\n    bool m_keep_signal_file_open;\n    bool m_keep_run_info_file_open;\n    bool m_keep_read_table_file_open;\n};\n\nclass FileWriterImpl;\n\nclass POD5_FORMAT_EXPORT FileWriter {\npublic:\n    FileWriter(std::unique_ptr<FileWriterImpl> && impl);\n    ~FileWriter();\n\n    std::string path() const;\n\n    pod5::Status close();\n\n    pod5::Status add_complete_read(\n        ReadData const & read_data,\n        gsl::span<std::int16_t const> const & signal);\n\n    /// \\brief Add a complete with rows already pre appended.\n    pod5::Status add_complete_read(\n        ReadData const & read_data,\n        gsl::span<std::uint64_t const> const & signal_rows,\n        std::uint64_t signal_duration);\n\n    pod5::Result<std::vector<SignalTableRowIndex>> add_signal(\n        Uuid const & read_id,\n        gsl::span<std::int16_t const> const & signal);\n\n    pod5::Result<SignalTableRowIndex> add_pre_compressed_signal(\n        Uuid const & read_id,\n        gsl::span<std::uint8_t const> const & signal_bytes,\n        std::uint32_t sample_count);\n\n    pod5::Result<std::pair<SignalTableRowIndex, SignalTableRowIndex>> add_signal_batch(\n        std::size_t row_count,\n        std::vector<std::shared_ptr<arrow::Array>> && columns,\n        bool final_batch);\n\n    // Find or create an end reason index representing this read end reason.\n    pod5::Result<EndReasonDictionaryIndex> lookup_end_reason(ReadEndReason end_reason) const;\n    pod5::Result<PoreDictionaryIndex> add_pore_type(std::string const & pore_type_data);\n    pod5::Result<RunInfoDictionaryIndex> add_run_info(RunInfoData const & run_info_data);\n\n    SignalType signal_type() const;\n    std::size_t signal_table_batch_size() const;\n\n    FileWriterImpl * impl() const { return m_impl.get(); };\n\nprivate:\n    std::unique_ptr<FileWriterImpl> m_impl;\n};\n\nPOD5_FORMAT_EXPORT pod5::Result<std::unique_ptr<FileWriter>> create_file_writer(\n    std::string const & path,\n    std::string const & writing_software_name,\n    FileWriterOptions const & options = {});\n\nstruct POD5_FORMAT_EXPORT RecoverFileOptions {\n    FileWriterOptions file_writer_options = {};\n\n    /// If this is set to true, recover_file will remove the following files\n    ///   * Temp files which we have successfully recovered data from.\n    ///   * Temp files which we have failed to recover data from and which hold no data.\n    ///   * Output file created during failed recovery.\n    bool cleanup = false;\n};\n\nstruct POD5_FORMAT_EXPORT RecoveredRowCounts final {\n    std::size_t signal = 0;\n    std::size_t run_info = 0;\n    std::size_t reads = 0;\n};\n\nstruct POD5_FORMAT_EXPORT CleanupError final {\n    std::string file_path;\n    std::string description;\n};\n\nstruct POD5_FORMAT_EXPORT RecoveryDetails final {\n    RecoveredRowCounts row_counts;\n    std::vector<CleanupError> cleanup_errors;\n};\n\nPOD5_FORMAT_EXPORT pod5::Result<RecoveryDetails> recover_file(\n    std::string const & src_path,\n    std::string const & dest_path,\n    RecoverFileOptions const & options = {});\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/flatbuffers/footer.fbs",
    "content": "namespace Minknow.ReadsFormat;\n\nenum ContentType:short {\n    // The Reads table (an Arrow table)\n    ReadsTable,\n    // The Signal table (an Arrow table)\n    SignalTable,\n    // An index for looking up data in the ReadsTable by read_id\n    ReadIdIndex,\n    // An index based on other columns and/or tables (it will need to be opened to find out what it indexes)\n    OtherIndex,\n    // The Run Info table (an Arrow table)\n    RunInfoTable,\n}\n\nenum Format:short {\n    // The Apache Feather V2 format, also known as the Apache Arrow IPC File format.\n    FeatherV2,\n}\n\n// Describes an embedded file.\ntable EmbeddedFile {\n    // The start of the embedded file\n    offset: int64;\n    // The length of the embedded file (excluding any padding)\n    length: int64;\n    // The format of the file\n    format: Format;\n    // What contents should be expected in the file\n    content_type: ContentType;\n}\n\ntable Footer {\n    // Must match the \"MINKNOW:file_identifier\" custom metadata entry in the schemas of the bundled tables.\n    file_identifier: string;\n    // A free-form description of the software that wrote the file, intended to help pin down the source of files that violate the specification.\n    software: string;\n    // The version of this specification that the table schemas are based on (1.0.0).\n    pod5_version: string;\n    // The Apache Arrow tables stored in the file.\n    contents: [ EmbeddedFile ];\n}\n"
  },
  {
    "path": "c++/pod5_format/internal/async_output_stream.h",
    "content": "#pragma once\n\n#include \"pod5_format/file_output_stream.h\"\n#include \"pod5_format/internal/tracing/tracing.h\"\n#include \"pod5_format/thread_pool.h\"\n\n#include <arrow/buffer.h>\n#include <arrow/util/future.h>\n#include <gsl/gsl-lite.hpp>\n\n#include <cassert>\n#include <condition_variable>\n#include <deque>\n#include <thread>\n\nnamespace pod5 {\n\nclass AsyncOutputStream : public FileOutputStream {\n    struct PrivateDummy {};\n\npublic:\n    static arrow::Result<std::shared_ptr<AsyncOutputStream>> make(\n        std::string const & file_path,\n        std::shared_ptr<ThreadPool> const & thread_pool,\n        bool flush_on_batch_complete,\n        arrow::MemoryPool * memory_pool = arrow::default_memory_pool(),\n        bool keep_file_open = true)\n    {\n        return std::make_shared<AsyncOutputStream>(\n            file_path,\n            thread_pool,\n            flush_on_batch_complete,\n            memory_pool,\n            keep_file_open,\n            PrivateDummy{});\n    }\n\n    ~AsyncOutputStream() { (void)Close(); }\n\n    arrow::Status Close() override\n    {\n        // flush all output\n        ARROW_RETURN_NOT_OK(Flush());\n\n        // and close stream\n        std::lock_guard<std::mutex> l{m_file_handle_mutex};\n        if (m_file_handle) {\n            fclose(m_file_handle);\n            m_file_handle = nullptr;\n        }\n        return arrow::Status::OK();\n    }\n\n    arrow::Future<> CloseAsync() override\n    {\n        ARROW_RETURN_NOT_OK(Close());\n        return FileOutputStream::CloseAsync();\n    }\n\n    arrow::Status Abort() override\n    {\n        std::lock_guard<std::mutex> l{m_file_handle_mutex};\n        if (m_file_handle) {\n            fclose(m_file_handle);\n            m_file_handle = nullptr;\n        }\n        return arrow::Status::OK();\n    }\n\n    arrow::Result<int64_t> Tell() const override\n    {\n        return m_actual_bytes_written - m_file_start_offset;\n    }\n\n    bool closed() const override { return m_file_handle == nullptr; }\n\n    arrow::Status Write(void const * data, int64_t nbytes) override\n    {\n        POD5_TRACE_FUNCTION();\n        ARROW_ASSIGN_OR_RAISE(\n            std::shared_ptr<arrow::Buffer> buffer, arrow::AllocateBuffer(nbytes, m_memory_pool));\n        auto const char_data = static_cast<std::uint8_t const *>(data);\n        std::copy(char_data, char_data + nbytes, buffer->mutable_data());\n        return Write(buffer);\n    }\n\n    arrow::Status Write(std::shared_ptr<arrow::Buffer> const & data) override\n    {\n        POD5_TRACE_FUNCTION();\n        if (m_has_error) {\n            return error();\n        }\n\n        std::size_t const BUFFER_SIZE = 10 * 1024 * 1024;  // 10mb pending writes max\n        while ((m_submitted_byte_writes - m_completed_byte_writes) > BUFFER_SIZE) {\n            std::this_thread::sleep_for(std::chrono::milliseconds(5));\n        }\n\n        m_submitted_byte_writes += data->size();\n        m_actual_bytes_written += data->size();\n\n        m_submitted_writes += 1;\n        m_strand->post([&, data] {\n            POD5_TRACE_FUNCTION();\n            if (m_has_error) {\n                return;\n            }\n\n            std::lock_guard<std::mutex> l{m_file_handle_mutex};\n            auto file_handle = get_or_open_file_handle(l);\n            if (!file_handle) {\n                set_error(arrow::Status::IOError(\"Failed to open file handle for writing\"));\n                return;\n            }\n            if (fwrite(data->data(), 1, (std::size_t)data->size(), file_handle)\n                != (std::size_t)data->size())\n            {\n                set_error(arrow::Status::IOError(\"Failed to write data to file\"));\n                return;\n            }\n            m_completed_byte_writes += data->size();\n\n            // Ensure we do this after editing all the other members, in order to prevent `Flush`\n            // returning until we are done.\n            m_completed_writes += 1;\n\n            // Close the file handle if we do not have further writes pending:\n            if (m_submitted_writes == m_completed_writes) {\n                close_file_handle(l);\n            }\n        });\n\n        return arrow::Status::OK();\n    }\n\n    arrow::Status Flush() override\n    {\n        POD5_TRACE_FUNCTION();\n        // Wait for our completed writes to match our submitted writes,\n        // this guarantees our async operations are finished.\n        auto wait_for_write_count = m_submitted_writes.load();\n        while (m_completed_writes.load() < wait_for_write_count && !m_has_error) {\n            std::this_thread::sleep_for(std::chrono::microseconds(10));\n        }\n\n        if (m_has_error) {\n            return error();\n        }\n\n        // No file handle so nothing to flush\n        std::lock_guard<std::mutex> l{m_file_handle_mutex};\n        if (!m_file_handle) {\n            return arrow::Status::OK();\n        }\n\n        if (fflush(m_file_handle) != 0) {\n            return arrow::Status::IOError(\"Error flushing file\");\n        }\n        return arrow::Status::OK();\n    }\n\n    void set_file_start_offset(std::size_t val) override { m_file_start_offset = val; }\n\n    arrow::Status batch_complete() override\n    {\n        if (m_flush_on_batch_complete) {\n            return Flush();\n        }\n        return arrow::Status::OK();\n    }\n\n    AsyncOutputStream(\n        std::string const & file_path,\n        std::shared_ptr<ThreadPool> const & thread_pool,\n        bool flush_on_batch_complete,\n        arrow::MemoryPool * memory_pool,\n        bool keep_file_open,\n        PrivateDummy)\n    : m_has_error{false}\n    , m_submitted_writes{0}\n    , m_completed_writes{0}\n    , m_submitted_byte_writes{0}\n    , m_completed_byte_writes{0}\n    , m_actual_bytes_written{0}\n    , m_flush_on_batch_complete(flush_on_batch_complete)\n    , m_file_path(file_path)\n    , m_keep_file_open(keep_file_open)\n    , m_file_start_offset{0}\n    , m_strand{thread_pool->create_strand()}\n    , m_memory_pool(memory_pool)\n    {\n        m_file_handle = fopen(m_file_path.c_str(), \"wb\");\n        if (!m_file_handle) {\n            set_error(arrow::Status::IOError(\"Failed to open file for writing: \", errno));\n        }\n        if (!m_keep_file_open) {\n            fclose(m_file_handle);\n            m_file_handle = nullptr;\n        }\n    }\n\nprivate:\n    arrow::MemoryPool * memory_pool() { return m_memory_pool; }\n\n    FILE * get_or_open_file_handle([[maybe_unused]] std::lock_guard<std::mutex> & lock)\n    {\n        if (m_file_handle) {\n            return m_file_handle;\n        }\n\n        m_file_handle = fopen(m_file_path.c_str(), \"ab\");\n        return m_file_handle;\n    }\n\n    void close_file_handle([[maybe_unused]] std::lock_guard<std::mutex> & lock)\n    {\n        if (m_file_handle && !m_keep_file_open) {\n            fclose(m_file_handle);\n            m_file_handle = nullptr;\n        }\n    }\n\n    void set_error(arrow::Status status)\n    {\n        assert(!status.ok());\n        {\n            std::lock_guard<std::mutex> l{m_error_mutex};\n            m_error = std::move(status);\n        }\n        m_has_error = true;\n    }\n\n    arrow::Status error() const\n    {\n        std::lock_guard<std::mutex> l{m_error_mutex};\n        return m_error;\n    }\n\n    std::atomic<bool> m_has_error;\n\n    std::atomic<std::size_t> m_submitted_writes;\n    std::atomic<std::size_t> m_completed_writes;\n    std::atomic<std::size_t> m_submitted_byte_writes;\n    std::atomic<std::size_t> m_completed_byte_writes;\n    // this represents the number of data bytes written (excluding any padding for alignment)\n    // used for truncating the file for instance\n    std::int64_t m_actual_bytes_written;\n\n    bool m_flush_on_batch_complete;\n\n    std::string m_file_path;\n    std::mutex m_file_handle_mutex;\n    FILE * m_file_handle{nullptr};\n    bool m_keep_file_open{false};\n\n    mutable std::mutex m_error_mutex;\n    arrow::Status m_error;\n\n    std::size_t m_file_start_offset;\n    std::shared_ptr<ThreadPoolStrand> m_strand;\n    arrow::MemoryPool * m_memory_pool;\n};\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/internal/combined_file_utils.h",
    "content": "#pragma once\n\n#include \"pod5_flatbuffers/footer_generated.h\"\n#include \"pod5_format/file_reader.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/uuid.h\"\n#include \"pod5_format/version.h\"\n\n#include <arrow/buffer.h>\n#include <arrow/io/file.h>\n#include <arrow/util/endian.h>\n#include <arrow/util/io_util.h>\n#include <flatbuffers/flatbuffers.h>\n\n#include <array>\n\nnamespace pod5 { namespace combined_file_utils {\n\nstatic constexpr std::array<char, 8>\n    FILE_SIGNATURE{'\\213', 'P', 'O', 'D', '\\r', '\\n', '\\032', '\\n'};\n\nstatic constexpr std::size_t header_size = 24;  // signature 8 bytes, section marker 16 bytes\n\ninline pod5::Status pad_file(\n    std::shared_ptr<arrow::io::OutputStream> const & sink,\n    std::uint32_t pad_to_size)\n{\n    ARROW_ASSIGN_OR_RAISE(auto const current_byte_location, sink->Tell());\n    auto const bytes_to_write = pad_to_size - (current_byte_location % pad_to_size);\n    if (bytes_to_write == pad_to_size) {\n        return pod5::Status::OK();\n    }\n\n    std::array<char, 128> zeroes{};\n    return sink->Write(zeroes.data(), bytes_to_write);\n}\n\ninline pod5::Status write_file_signature(std::shared_ptr<arrow::io::OutputStream> const & sink)\n{\n    return sink->Write(FILE_SIGNATURE.data(), FILE_SIGNATURE.size());\n}\n\ninline pod5::Status write_section_marker(\n    std::shared_ptr<arrow::io::OutputStream> const & sink,\n    Uuid const & section_marker)\n{\n    return sink->Write(section_marker.data(), section_marker.size());\n}\n\ninline pod5::Status write_combined_header(\n    std::shared_ptr<arrow::io::OutputStream> const & sink,\n    Uuid const & section_marker)\n{\n    ARROW_RETURN_NOT_OK(write_file_signature(sink));\n    return write_section_marker(sink, section_marker);\n}\n\ninline pod5::Status write_footer_magic(std::shared_ptr<arrow::io::OutputStream> const & sink)\n{\n    return sink->Write(\"FOOTER\\0\\0\", 8);\n}\n\nstruct FileInfo {\n    std::int64_t file_start_offset = 0;\n    std::int64_t file_length = 0;\n};\n\nstruct ParsedFileInfo : FileInfo {\n    std::string file_path;\n    std::shared_ptr<arrow::io::RandomAccessFile> file;\n\n    arrow::Status from_full_file(std::string in_file_path)\n    {\n        file_path = in_file_path;\n        ARROW_ASSIGN_OR_RAISE(\n            file, arrow::io::MemoryMappedFile::Open(in_file_path, arrow::io::FileMode::READ));\n        file_start_offset = 0;\n        ARROW_ASSIGN_OR_RAISE(file_length, file->GetSize());\n        return arrow::Status::OK();\n    }\n};\n\ninline pod5::Result<std::int64_t> write_footer_flatbuffer(\n    std::shared_ptr<arrow::io::OutputStream> const & sink,\n    Uuid const & file_identifier,\n    std::string const & software_name,\n    FileInfo const & signal_table,\n    FileInfo const & run_info_table,\n    FileInfo const & reads_table)\n{\n    flatbuffers::FlatBufferBuilder builder(1024);\n\n    auto signal_file = Minknow::ReadsFormat::CreateEmbeddedFile(\n        builder,\n        signal_table.file_start_offset,\n        signal_table.file_length,\n        Minknow::ReadsFormat::Format_FeatherV2,\n        Minknow::ReadsFormat::ContentType_SignalTable);\n\n    auto run_info_file = Minknow::ReadsFormat::CreateEmbeddedFile(\n        builder,\n        run_info_table.file_start_offset,\n        run_info_table.file_length,\n        Minknow::ReadsFormat::Format_FeatherV2,\n        Minknow::ReadsFormat::ContentType_RunInfoTable);\n\n    auto reads_file = Minknow::ReadsFormat::CreateEmbeddedFile(\n        builder,\n        reads_table.file_start_offset,\n        reads_table.file_length,\n        Minknow::ReadsFormat::Format_FeatherV2,\n        Minknow::ReadsFormat::ContentType_ReadsTable);\n\n    std::vector<flatbuffers::Offset<Minknow::ReadsFormat::EmbeddedFile>> const files{\n        signal_file, run_info_file, reads_file};\n    auto footer = Minknow::ReadsFormat::CreateFooterDirect(\n        builder,\n        to_string(file_identifier).c_str(),\n        software_name.c_str(),\n        Pod5Version.c_str(),\n        &files);\n\n    builder.Finish(footer);\n    ARROW_RETURN_NOT_OK(sink->Write(builder.GetBufferPointer(), builder.GetSize()));\n    return builder.GetSize();\n}\n\ninline pod5::Status write_footer(\n    std::shared_ptr<arrow::io::OutputStream> const & sink,\n    Uuid const & section_marker,\n    Uuid const & file_identifier,\n    std::string const & software_name,\n    FileInfo const & signal_table,\n    FileInfo const & run_info_table,\n    FileInfo const & reads_table)\n{\n    ARROW_RETURN_NOT_OK(write_footer_magic(sink));\n    ARROW_ASSIGN_OR_RAISE(\n        std::int64_t length,\n        write_footer_flatbuffer(\n            sink, file_identifier, software_name, signal_table, run_info_table, reads_table));\n    ARROW_RETURN_NOT_OK(pad_file(sink, 8));\n\n    std::int64_t paded_flatbuffer_size = arrow::bit_util::ToLittleEndian(length);\n    ARROW_RETURN_NOT_OK(sink->Write(&paded_flatbuffer_size, sizeof(paded_flatbuffer_size)));\n\n    ARROW_RETURN_NOT_OK(write_section_marker(sink, section_marker));\n    return write_file_signature(sink);\n}\n\nstruct ParsedFooter {\n    Uuid file_identifier;\n    std::string software_name;\n    std::string writer_pod5_version;\n\n    ParsedFileInfo run_info_table;\n    ParsedFileInfo reads_table;\n    ParsedFileInfo signal_table;\n};\n\ninline void bind_footer_file(\n    ParsedFooter & footer,\n    std::shared_ptr<arrow::io::RandomAccessFile> const & file)\n{\n    footer.reads_table.file = file;\n    footer.run_info_table.file = file;\n    footer.signal_table.file = file;\n}\n\ninline pod5::Status check_signature(\n    std::shared_ptr<arrow::io::RandomAccessFile> const & file,\n    std::int64_t offset_in_file)\n{\n    std::array<char, sizeof(FILE_SIGNATURE)> read_signature;\n    ARROW_ASSIGN_OR_RAISE(\n        auto read_bytes,\n        file->ReadAt(offset_in_file, read_signature.size(), read_signature.data()));\n    if (read_bytes != (std::int16_t)read_signature.size() || read_signature != FILE_SIGNATURE) {\n        return arrow::Status::IOError(\"Invalid signature in file\");\n    }\n\n    return arrow::Status::OK();\n}\n\ninline pod5::Result<Minknow::ReadsFormat::Footer const *> read_footer_flatbuffer(\n    std::vector<std::uint8_t> const & footer_data)\n{\n    auto verifier = flatbuffers::Verifier(footer_data.data(), footer_data.size());\n    if (!verifier.VerifyBuffer<Minknow::ReadsFormat::Footer>()) {\n        return arrow::Status::IOError(\"Invalid footer found in file\");\n    }\n    return flatbuffers::GetRoot<Minknow::ReadsFormat::Footer>(footer_data.data());\n}\n\ninline pod5::Result<ParsedFooter> read_footer(\n    std::string const & file_path,\n    std::shared_ptr<arrow::io::RandomAccessFile> const & file)\n{\n    // Verify signature at start and end of file:\n    ARROW_RETURN_NOT_OK(check_signature(file, 0));\n    ARROW_ASSIGN_OR_RAISE(auto const file_size, file->GetSize());\n    ARROW_RETURN_NOT_OK(check_signature(file, file_size - FILE_SIGNATURE.size()));\n\n    auto footer_length_data_end = file_size;\n    footer_length_data_end -= FILE_SIGNATURE.size();\n    footer_length_data_end -= sizeof(Uuid);\n\n    std::int64_t footer_length = 0;\n    ARROW_RETURN_NOT_OK(file->ReadAt(\n        footer_length_data_end - sizeof(footer_length), sizeof(footer_length), &footer_length));\n    footer_length = arrow::bit_util::FromLittleEndian(footer_length);\n    if (footer_length < 0\n        || static_cast<std::size_t>(footer_length) > footer_length_data_end - sizeof(footer_length))\n    {\n        return arrow::Status::IOError(\"Invalid footer length\");\n    }\n\n    std::vector<std::uint8_t> footer_data;\n    footer_data.resize(footer_length);\n    ARROW_ASSIGN_OR_RAISE(\n        auto read_bytes,\n        file->ReadAt(\n            footer_length_data_end - sizeof(footer_length) - footer_length,\n            footer_length,\n            footer_data.data()));\n    if (read_bytes != footer_length) {\n        return arrow::Status::IOError(\"Failed to read footer data\");\n    }\n    ARROW_ASSIGN_OR_RAISE(auto fb_footer, read_footer_flatbuffer(footer_data));\n\n    ParsedFooter footer;\n    if (!fb_footer->file_identifier()) {\n        return arrow::Status::IOError(\"Invalid footer file_identifier\");\n    }\n    auto const identifier = Uuid::from_string(fb_footer->file_identifier()->str());\n    if (!identifier) {\n        return Status::IOError(\n            \"Invalid file_identifier in file: '\", fb_footer->file_identifier()->str(), \"'\");\n    }\n    footer.file_identifier = *identifier;\n\n    if (!fb_footer->software()) {\n        return arrow::Status::IOError(\"Invalid footer software\");\n    }\n    footer.software_name = fb_footer->software()->str();\n\n    if (!fb_footer->pod5_version()) {\n        return arrow::Status::IOError(\"Invalid footer pod5_version\");\n    }\n    footer.writer_pod5_version = fb_footer->pod5_version()->str();\n\n    if (!fb_footer->contents()) {\n        return arrow::Status::IOError(\"Invalid footer contents\");\n    }\n    for (auto const embedded_file : *fb_footer->contents()) {\n        if (embedded_file->format() != Minknow::ReadsFormat::Format_FeatherV2) {\n            return arrow::Status::IOError(\"Invalid embedded file format\");\n        }\n        switch (embedded_file->content_type()) {\n        case Minknow::ReadsFormat::ContentType_RunInfoTable:\n            footer.run_info_table.file_start_offset = embedded_file->offset();\n            footer.run_info_table.file_length = embedded_file->length();\n            footer.run_info_table.file = file;\n            footer.run_info_table.file_path = file_path;\n            break;\n        case Minknow::ReadsFormat::ContentType_ReadsTable:\n            footer.reads_table.file_start_offset = embedded_file->offset();\n            footer.reads_table.file_length = embedded_file->length();\n            footer.reads_table.file = file;\n            footer.reads_table.file_path = file_path;\n            break;\n        case Minknow::ReadsFormat::ContentType_SignalTable:\n            footer.signal_table.file_start_offset = embedded_file->offset();\n            footer.signal_table.file_length = embedded_file->length();\n            footer.signal_table.file = file;\n            footer.signal_table.file_path = file_path;\n            break;\n\n        default:\n            return arrow::Status::IOError(\"Unknown embedded file type\");\n        }\n    }\n\n    return footer;\n}\n\nclass SubFile : public arrow::io::internal::RandomAccessFileConcurrencyWrapper<SubFile> {\npublic:\n    SubFile(\n        std::shared_ptr<arrow::io::RandomAccessFile> main_file,\n        std::int64_t sub_file_offset,\n        std::int64_t sub_file_length)\n    : m_file(std::move(main_file))\n    , m_sub_file_offset(sub_file_offset)\n    , m_sub_file_length(sub_file_length)\n    {\n    }\n\nprotected:\n    arrow::Status DoClose() { return m_file->Close(); }\n\n    bool closed() const override { return m_file->closed(); }\n\n    arrow::Result<std::int64_t> DoTell() const\n    {\n        ARROW_ASSIGN_OR_RAISE(auto t, m_file->Tell());\n        return t - m_sub_file_offset;\n    }\n\n    arrow::Status DoSeek(int64_t offset)\n    {\n        if (offset < 0 || offset > m_sub_file_length) {\n            return arrow::Status::IOError(\"Invalid offset into SubFile\");\n        }\n        offset += m_sub_file_offset;\n        return m_file->Seek(offset);\n    }\n\n    arrow::Result<std::int64_t> DoRead(int64_t length, void * data)\n    {\n        ARROW_ASSIGN_OR_RAISE(auto pos, m_file->Tell());\n        int64_t const remaining = m_sub_file_offset + m_sub_file_length - pos;\n        length = std::min(remaining, length);\n        return m_file->Read(length, data);\n    }\n\n    arrow::Result<std::shared_ptr<arrow::Buffer>> DoRead(int64_t length)\n    {\n        ARROW_ASSIGN_OR_RAISE(auto pos, m_file->Tell());\n        int64_t const remaining = m_sub_file_offset + m_sub_file_length - pos;\n        length = std::min(remaining, length);\n        return m_file->Read(length);\n    }\n\n    Result<int64_t> DoReadAt(int64_t position, int64_t nbytes, void * out)\n    {\n        if (position < 0 || position > m_sub_file_length) {\n            return arrow::Status::IOError(\"Invalid offset into SubFile\");\n        }\n        int64_t const remaining = m_sub_file_length - position;\n        nbytes = std::min(nbytes, remaining);\n        return m_file->ReadAt(position + m_sub_file_offset, nbytes, out);\n    }\n\n    Result<std::shared_ptr<arrow::Buffer>> DoReadAt(int64_t position, int64_t nbytes)\n    {\n        if (position < 0 || position > m_sub_file_length) {\n            return arrow::Status::IOError(\"Invalid offset into SubFile\");\n        }\n        int64_t const remaining = m_sub_file_length - position;\n        nbytes = std::min(nbytes, remaining);\n        return m_file->ReadAt(position + m_sub_file_offset, nbytes);\n    }\n\n    arrow::Result<std::int64_t> DoGetSize() { return m_sub_file_length; }\n\nprivate:\n    friend RandomAccessFileConcurrencyWrapper<SubFile>;\n\n    std::shared_ptr<arrow::io::RandomAccessFile> m_file;\n    std::int64_t m_sub_file_offset;\n    std::int64_t m_sub_file_length;\n};\n\ninline arrow::Result<std::shared_ptr<SubFile>> open_sub_file(ParsedFileInfo file_info)\n{\n    if (!file_info.file) {\n        return arrow::Status::Invalid(\"Failed to open file from footer\");\n    }\n    ARROW_ASSIGN_OR_RAISE(auto file_size, file_info.file->GetSize());\n    if (file_info.file_length < 0 || file_info.file_length > file_size\n        || file_info.file_start_offset > file_size - file_info.file_length)\n    {\n        return arrow::Status::Invalid(\"Bad footer info\");\n    }\n    // Restrict our open file to just the run info section:\n    auto sub_file = std::make_shared<SubFile>(\n        file_info.file, file_info.file_start_offset, file_info.file_length);\n    ARROW_RETURN_NOT_OK(sub_file->Seek(0));\n    return sub_file;\n}\n\ninline arrow::Result<std::shared_ptr<SubFile>> open_sub_file(\n    std::shared_ptr<arrow::io::RandomAccessFile> const & file,\n    std::size_t offset)\n{\n    if (!file) {\n        return arrow::Status::Invalid(\"Failed to open file from footer\");\n    }\n\n    ARROW_ASSIGN_OR_RAISE(auto file_size, file->GetSize());\n\n    // Restrict our open file to just the run info section:\n    auto sub_file = std::make_shared<SubFile>(file, offset, file_size - offset);\n    ARROW_RETURN_NOT_OK(sub_file->Seek(0));\n    return sub_file;\n}\n\nenum class SubFileCleanup { CleanupOriginalFile, LeaveOrignalFile };\n\ninline arrow::Result<combined_file_utils::FileInfo> write_file(\n    arrow::MemoryPool * pool,\n    std::shared_ptr<arrow::io::FileOutputStream> const & file,\n    FileLocation const & file_location,\n    SubFileCleanup cleanup_mode)\n{\n    combined_file_utils::FileInfo table_data;\n    // Record file start location in bytes within the main file:\n    ARROW_ASSIGN_OR_RAISE(table_data.file_start_offset, file->Tell());\n\n    {\n        // Stream out the reads table into the main file:\n        ARROW_ASSIGN_OR_RAISE(\n            auto reads_table_file_in, arrow::io::ReadableFile::Open(file_location.file_path, pool));\n        ARROW_RETURN_NOT_OK(reads_table_file_in->Seek(file_location.offset));\n        std::int64_t copied_bytes = 0;\n        std::int64_t target_chunk_size = 10 * 1024 * 1024;  // Read in 10MB of data at a time\n        while (copied_bytes < std::int64_t(file_location.size)) {\n            std::size_t const to_read =\n                std::min<std::int64_t>(file_location.size - copied_bytes, target_chunk_size);\n            ARROW_ASSIGN_OR_RAISE(auto const read_buffer, reads_table_file_in->Read(to_read));\n            copied_bytes += read_buffer->size();\n            ARROW_RETURN_NOT_OK(file->Write(read_buffer));\n        }\n\n        // Store the reads file length for later reading:\n        ARROW_ASSIGN_OR_RAISE(table_data.file_length, file->Tell());\n        table_data.file_length -= table_data.file_start_offset;\n    }\n\n    if (cleanup_mode == SubFileCleanup::CleanupOriginalFile) {\n        // Clean up the tmp read path:\n        ARROW_ASSIGN_OR_RAISE(\n            auto arrow_path,\n            ::arrow::internal::PlatformFilename::FromString(file_location.file_path));\n        ARROW_RETURN_NOT_OK(arrow::internal::DeleteFile(arrow_path));\n    }\n\n    return table_data;\n}\n\ninline arrow::Result<combined_file_utils::FileInfo> write_file_and_marker(\n    arrow::MemoryPool * pool,\n    std::shared_ptr<arrow::io::FileOutputStream> const & file,\n    FileLocation const & file_location,\n    SubFileCleanup cleanup_mode,\n    Uuid const & section_marker)\n{\n    ARROW_ASSIGN_OR_RAISE(auto file_info, write_file(pool, file, file_location, cleanup_mode));\n    // Pad file to 8 bytes and mark section:\n    ARROW_RETURN_NOT_OK(combined_file_utils::pad_file(file, 8));\n    ARROW_RETURN_NOT_OK(combined_file_utils::write_section_marker(file, section_marker));\n    return file_info;\n}\n\n}}  // namespace pod5::combined_file_utils\n"
  },
  {
    "path": "c++/pod5_format/internal/linux_output_stream.h",
    "content": "#pragma once\n\n#include \"pod5_format/file_output_stream.h\"\n#include \"pod5_format/internal/tracing/tracing.h\"\n#include \"pod5_format/io_manager.h\"\n\n#include <arrow/buffer.h>\n#include <arrow/util/future.h>\n#include <gsl/gsl-lite.hpp>\n\n#include <condition_variable>\n#include <deque>\n\n#ifdef __linux__\n#include <fcntl.h>\n#include <unistd.h>\n#endif\n\nnamespace pod5 {\n\nnamespace {\nconstexpr size_t fallocate_chunk = 50 * 256 * IOManager::Alignment;  // 50MB\n}  // namespace\n\n#ifdef __linux__\nclass LinuxOutputStream : public FileOutputStream {\n    struct PrivateDummy {};\n\npublic:\n    static arrow::Result<std::shared_ptr<LinuxOutputStream>> make(\n        std::string const & file_path,\n        std::shared_ptr<IOManager> const & io_manager,\n        std::size_t write_chunk_size,\n        bool use_directio,\n        bool use_syncio,\n        bool flush_on_batch_complete,\n        bool keep_file_open = true)\n    {\n        auto flags = O_RDWR | O_CREAT;\n        if (use_directio) {\n            flags |= O_DIRECT;\n        }\n\n        if (use_syncio) {\n            flags |= O_SYNC;\n        }\n\n        auto const initial_file_descriptor = open(file_path.c_str(), flags, 0644);\n        if (initial_file_descriptor < 0) {\n            return arrow::Status::Invalid(\"Failed to open file\");\n        }\n\n        return std::make_shared<LinuxOutputStream>(\n            file_path,\n            initial_file_descriptor,\n            flags,\n            io_manager,\n            write_chunk_size,\n            keep_file_open,\n            flush_on_batch_complete,\n            PrivateDummy{});\n    }\n\n    ~LinuxOutputStream() { (void)Close(); }\n\n    arrow::Status Close() override\n    {\n        // flush all output\n        ARROW_RETURN_NOT_OK(Flush());\n\n        while (!m_queued_writes.empty()) {\n            ARROW_RETURN_NOT_OK(process_queued_writes());\n\n            if (!m_queued_writes.empty()) {\n                ARROW_RETURN_NOT_OK(m_io_manager->wait_for_event(std::chrono::seconds(1)));\n            }\n        }\n\n        std::lock_guard<std::mutex> l{m_file_handle_mutex};\n        ARROW_ASSIGN_OR_RAISE(auto const file_descriptor, get_or_open_fd(l));\n\n        // truncate excess data\n        if (::ftruncate(file_descriptor, m_bytes_written) < 0) {\n            return arrow::Status::IOError(\"Failed to truncate file\");\n        }\n\n        // and close stream\n        return close_fd(l, true);\n    }\n\n    arrow::Future<> CloseAsync() override { return Close(); }\n\n    arrow::Status Abort() override\n    {\n        std::lock_guard<std::mutex> l{m_file_handle_mutex};\n        return close_fd(l, true);\n    }\n\n    arrow::Result<int64_t> Tell() const override { return m_bytes_written - m_file_start_offset; }\n\n    bool closed() const override { return m_file_descriptor == -1; }\n\n    arrow::Status Write(void const * data, int64_t nbytes) override\n    {\n        ARROW_RETURN_NOT_OK(allocate_file_space(nbytes));\n\n        auto remaining_data = gsl::make_span(reinterpret_cast<std::uint8_t const *>(data), nbytes);\n        while (!remaining_data.empty()) {\n            ARROW_ASSIGN_OR_RAISE(\n                remaining_data, m_aligned_buffer.consume_until_full(remaining_data));\n\n            if (m_aligned_buffer.is_full()) {\n                ARROW_RETURN_NOT_OK(flush_writes(FlushMode::AlignedWrites));\n            }\n        }\n\n        m_bytes_written += nbytes;\n\n        return arrow::Status::OK();\n    }\n\n    arrow::Status Write(std::shared_ptr<arrow::Buffer> const & data) override\n    {\n        ARROW_RETURN_NOT_OK(allocate_file_space(data->size()));\n\n        auto remaining_data = gsl::make_span(data->data(), data->size());\n        while (!remaining_data.empty()) {\n            ARROW_ASSIGN_OR_RAISE(\n                remaining_data, m_aligned_buffer.consume_until_full(remaining_data));\n\n            if (m_aligned_buffer.is_full()) {\n                ARROW_RETURN_NOT_OK(flush_writes(FlushMode::AlignedWrites));\n            }\n        }\n\n        m_bytes_written += data->size();\n\n        return arrow::Status::OK();\n    }\n\n    arrow::Status batch_complete() override\n    {\n        if (m_flush_on_batch_complete) {\n            return flush_writes(FlushMode::AllWrites);\n        }\n        return arrow::Status::OK();\n    }\n\n    arrow::Status Flush() override\n    {\n        ARROW_RETURN_NOT_OK(flush_writes(FlushMode::AllWrites));\n\n        std::lock_guard<std::mutex> l{m_file_handle_mutex};\n        if (m_file_descriptor < 0) {\n            return arrow::Status::OK();\n        }\n\n        if (fsync(m_file_descriptor) < 0) {\n            return arrow::Status::IOError(\"Error flushing file\");\n        }\n\n        return arrow::Status::OK();\n    }\n\n    void set_file_start_offset(std::size_t val) override { m_file_start_offset = val; }\n\n    void set_flush_on_batch_complete(bool flush_on_batch_complete)\n    {\n        m_flush_on_batch_complete = flush_on_batch_complete;\n    }\n\n    LinuxOutputStream(\n        std::string const & file_path,\n        int initial_file_descriptor,\n        int flags,\n        std::shared_ptr<IOManager> const & io_manager,\n        std::size_t write_chunk_size,\n        bool keep_file_open,\n        bool flush_on_batch_complete,\n        PrivateDummy)\n    : m_file_path(file_path)\n    , m_flags(flags)\n    , m_file_descriptor(initial_file_descriptor)\n    , m_keep_file_open(keep_file_open)\n    , m_aligned_buffer(write_chunk_size, io_manager)\n    , m_io_manager(io_manager)\n    , m_flush_on_batch_complete(flush_on_batch_complete)\n    {\n        if (!m_keep_file_open) {\n            close(m_file_descriptor);\n            m_file_descriptor = -1;\n        }\n    }\n\nprotected:\n    enum class FlushMode { AllWrites, AlignedWrites };\n\n    class AlignedBuffer {\n    public:\n        AlignedBuffer(std::size_t capacity, std::shared_ptr<IOManager> const & io_manager)\n        : m_io_manager(io_manager)\n        , m_capacity(capacity)\n        {\n        }\n\n        // Copy input span to the end of the buffer until this buffer is full.\n        //\n        // Return any remaining buffer.\n        arrow::Result<gsl::span<std::uint8_t const>> consume_until_full(\n            gsl::span<std::uint8_t const> input)\n        {\n            ARROW_RETURN_NOT_OK(ensure_next_write());\n\n            auto & buffer = m_next_write->get_buffer();\n            assert((std::size_t)buffer.capacity() >= m_capacity);\n            auto const remaining_buffer_bytes = buffer.capacity() - buffer.size();\n            auto const to_copy = std::min(input.size(), (std::size_t)remaining_buffer_bytes);\n\n            std::copy(\n                input.begin(), input.begin() + to_copy, buffer.mutable_data() + buffer.size());\n            ARROW_RETURN_NOT_OK(buffer.Resize(buffer.size() + to_copy, false));\n\n            return input.subspan(to_copy);\n        }\n\n        // Find if the buffer is full (m_size == m_capacity)\n        bool is_full() const\n        {\n            if (!m_next_write) {\n                return false;\n            }\n\n            return (std::size_t)m_next_write->get_buffer().size() == m_capacity;\n        }\n\n        arrow::Result<std::shared_ptr<QueuedWrite>> release_all_writes_and_align(\n            std::size_t * out_aligned_write_size)\n        {\n            ARROW_RETURN_NOT_OK(ensure_next_write());\n\n            *out_aligned_write_size = aligned_write_size(m_next_write->get_buffer().size());\n\n            auto result_write = std::move(m_next_write);\n            auto & result_write_buffer = result_write->get_buffer();\n            ARROW_ASSIGN_OR_RAISE(m_next_write, m_io_manager->allocate_new_write(m_capacity));\n            auto & next_write_buffer = m_next_write->get_buffer();\n            std::copy(\n                result_write_buffer.data() + *out_aligned_write_size,\n                result_write_buffer.data() + result_write_buffer.size(),\n                next_write_buffer.mutable_data());\n            ARROW_RETURN_NOT_OK(next_write_buffer.Resize(\n                result_write_buffer.size() - *out_aligned_write_size, false));\n\n            // Ensure the result write buffer is aligned to our write alignment.\n            auto const result_unaligned_size = result_write_buffer.size();\n            auto result_aligned_size =\n                result_unaligned_size + (-result_unaligned_size & (IOManager::Alignment - 1));\n            ARROW_RETURN_NOT_OK(result_write_buffer.Resize(result_aligned_size, false));\n            assert(result_write_buffer.size() % IOManager::Alignment == 0);\n\n            result_write->set_state(QueuedWrite::WriteState::ReadyForWrite);\n            return result_write;\n        }\n\n        arrow::Result<std::shared_ptr<QueuedWrite>> release_aligned_writes()\n        {\n            ARROW_RETURN_NOT_OK(ensure_next_write());\n\n            auto result_write = std::move(m_next_write);\n            auto & result_write_buffer = result_write->get_buffer();\n            ARROW_ASSIGN_OR_RAISE(m_next_write, m_io_manager->allocate_new_write(m_capacity));\n            auto & next_write_buffer = m_next_write->get_buffer();\n\n            auto aligned_size = aligned_write_size(result_write_buffer.size());\n            std::copy(\n                result_write_buffer.data() + aligned_size,\n                result_write_buffer.data() + result_write_buffer.size(),\n                next_write_buffer.mutable_data());\n            ARROW_RETURN_NOT_OK(result_write_buffer.Resize(aligned_size, false));\n            ARROW_RETURN_NOT_OK(\n                next_write_buffer.Resize(result_write_buffer.size() - aligned_size, false));\n\n            result_write->set_state(QueuedWrite::WriteState::ReadyForWrite);\n            return result_write;\n        }\n\n    private:\n        std::size_t aligned_write_size(std::size_t input_size) const\n        {\n            return (input_size / IOManager::Alignment) * IOManager::Alignment;\n        }\n\n        arrow::Status ensure_next_write()\n        {\n            if (m_next_write) {\n                return arrow::Status::OK();\n            }\n\n            ARROW_ASSIGN_OR_RAISE(m_next_write, m_io_manager->allocate_new_write(m_capacity));\n            assert((std::size_t)m_next_write->get_buffer().capacity() >= m_capacity);\n            return arrow::Status::OK();\n        }\n\n        std::shared_ptr<QueuedWrite> m_next_write;\n        std::shared_ptr<IOManager> m_io_manager;\n        std::size_t m_capacity;\n    };\n\n    arrow::Result<int> get_or_open_fd([[maybe_unused]] std::lock_guard<std::mutex> & lock)\n    {\n        if (m_file_descriptor >= 0) {\n            return m_file_descriptor;\n        }\n\n        m_file_descriptor = open(m_file_path.c_str(), m_flags, 0644);\n        if (m_file_descriptor < 0) {\n            return arrow::Status::IOError(\"Failed to open file for writing\");\n        }\n        return m_file_descriptor;\n    }\n\n    arrow::Status close_fd([[maybe_unused]] std::lock_guard<std::mutex> & lock, bool force = false)\n    {\n        if (m_keep_file_open && !force) {\n            return arrow::Status::OK();\n        }\n\n        if (close(m_file_descriptor) != 0) {\n            return arrow::Status::IOError(\"Error closing file\");\n        }\n        m_file_descriptor = -1;\n        return arrow::Status::OK();\n    }\n\n    arrow::Status flush_writes(FlushMode flush_mode)\n    {\n        std::size_t write_offset{};\n        std::shared_ptr<QueuedWrite> released_data;\n\n        if (flush_mode == FlushMode::AllWrites) {\n            std::size_t aligned_write_size = 0;\n            ARROW_ASSIGN_OR_RAISE(\n                released_data, m_aligned_buffer.release_all_writes_and_align(&aligned_write_size));\n            write_offset = m_bytes_submitted_to_manager;\n            m_bytes_submitted_to_manager += aligned_write_size;\n        } else if (flush_mode == FlushMode::AlignedWrites) {\n            ARROW_ASSIGN_OR_RAISE(released_data, m_aligned_buffer.release_aligned_writes());\n            write_offset = m_bytes_submitted_to_manager;\n            m_bytes_submitted_to_manager += released_data->get_buffer().size();\n        } else {\n            assert(false);\n            return arrow::Status::Invalid(\"Invalid FlushMode Passed.\");\n        }\n\n        assert(released_data->get_buffer().size() % IOManager::Alignment == 0);\n\n        if (released_data->get_buffer().size() == 0) {\n            return arrow::Status::OK();\n        }\n\n        std::lock_guard<std::mutex> lock(m_file_handle_mutex);\n        ARROW_ASSIGN_OR_RAISE(auto const file_descriptor, get_or_open_fd(lock));\n        released_data->prepare_for_write(file_descriptor, write_offset);\n\n        m_queued_writes.emplace_back(released_data);\n        ARROW_RETURN_NOT_OK(m_io_manager->write_buffer(std::move(released_data)));\n\n        ARROW_RETURN_NOT_OK(process_queued_writes());\n\n        if (m_queued_writes.empty()) {\n            return close_fd(lock);\n        } else {\n            // If we have queued writes, we keep the file open.\n            return arrow::Status::OK();\n        }\n    }\n\n    arrow::Status process_queued_writes()\n    {\n        for (auto it = m_queued_writes.begin(); it != m_queued_writes.end();) {\n            if ((*it)->state() == QueuedWrite::WriteState::Completed) {\n                ARROW_RETURN_NOT_OK(m_io_manager->return_used_write(std::move(*it)));\n                it = m_queued_writes.erase(it);\n            } else {\n                ++it;\n            }\n        }\n\n        return arrow::Status::OK();\n    }\n\n    arrow::Status allocate_file_space(std::size_t new_write_size)\n    {\n        auto new_total_size = m_bytes_written + new_write_size;\n        if (new_total_size > m_fallocate_offset) {\n            // reserve more space before continuing\n            m_fallocate_offset += fallocate_chunk;\n\n            std::lock_guard<std::mutex> lock(m_file_handle_mutex);\n            ARROW_ASSIGN_OR_RAISE(auto const file_descriptor, get_or_open_fd(lock));\n\n            // If this fails, we will just write less optimially, so we ignore the result.\n            ::fallocate(file_descriptor, 0, m_fallocate_offset, fallocate_chunk);\n        }\n\n        return arrow::Status::OK();\n    }\n\n    std::string m_file_path;\n    int m_flags;\n    std::mutex m_file_handle_mutex;\n    int m_file_descriptor;\n    bool m_keep_file_open{false};\n\n    AlignedBuffer m_aligned_buffer;\n    std::vector<std::shared_ptr<QueuedWrite>> m_queued_writes;\n    std::shared_ptr<IOManager> m_io_manager;\n    std::size_t m_fallocate_offset{0};\n    std::size_t m_file_start_offset{0};\n    std::size_t m_bytes_written{0};\n    std::size_t m_bytes_submitted_to_manager{0};\n    bool m_flush_on_batch_complete;\n};\n#endif\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/internal/tracing/tracing.h",
    "content": "#pragma once\n\n#define POD5_TRACE_FUNCTION()\n"
  },
  {
    "path": "c++/pod5_format/io_manager.cpp",
    "content": "#include \"pod5_format/io_manager.h\"\n\n#ifdef __linux__\n#include <unistd.h>\n#endif\n\nnamespace pod5 {\n\n#ifdef __linux__\nclass IOManagerSyncImpl : public IOManager {\npublic:\n    IOManagerSyncImpl(arrow::MemoryPool * memory_pool) : m_memory_pool(memory_pool) {}\n\n    arrow::Result<std::shared_ptr<QueuedWrite>> allocate_new_write(std::size_t capacity) override\n    {\n        if (m_queued_writes.size()) {\n            auto new_write = m_queued_writes.back();\n            m_queued_writes.pop_back();\n            ARROW_RETURN_NOT_OK(new_write->reset_queued_write());\n            ARROW_RETURN_NOT_OK(new_write->get_buffer().Reserve(capacity));\n            assert((std::size_t)new_write->get_buffer().capacity() >= capacity);\n            return new_write;\n        }\n\n        ARROW_ASSIGN_OR_RAISE(\n            std::unique_ptr<arrow::ResizableBuffer> buffer,\n            arrow::AllocateResizableBuffer(capacity, IOManager::Alignment, m_memory_pool));\n        ARROW_RETURN_NOT_OK(buffer->Resize(0, false));\n        assert((std::size_t)buffer->capacity() >= capacity);\n        assert((std::size_t)buffer->size() == 0);\n        return std::make_shared<QueuedWrite>(std::move(buffer));\n    }\n\n    arrow::Status return_used_write(std::shared_ptr<QueuedWrite> && used_write) override\n    {\n        if (m_queued_writes.size() < CachedBufferCount) {\n            m_queued_writes.push_back(std::move(used_write));\n        }\n        used_write.reset();\n        return arrow::Status::OK();\n    }\n\n    arrow::Status write_buffer(std::shared_ptr<QueuedWrite> && data) override\n    {\n        auto result = lseek(data->file_descriptor(), data->file_offset(), SEEK_SET);\n        if (result < 0) {\n            return arrow::Status::IOError(\"Error seeking in file\");\n        }\n\n        result =\n            write(data->file_descriptor(), data->get_buffer().data(), data->get_buffer().size());\n        if (result < 0) {\n            return arrow::Status::IOError(\n                \"Error writing to file: \",\n                errno,\n                \" desc: \",\n                data->file_descriptor(),\n                \" offset: \",\n                data->file_offset(),\n                \" size: \",\n                data->get_buffer().size());\n        }\n\n        data->set_state(QueuedWrite::WriteState::Completed);\n\n        return {};\n    }\n\nprivate:\n    arrow::MemoryPool * m_memory_pool;\n    std::vector<std::shared_ptr<QueuedWrite>> m_queued_writes;\n};\n\narrow::Result<std::shared_ptr<IOManager>> make_sync_io_manager(arrow::MemoryPool * memory_pool)\n{\n    return std::make_shared<IOManagerSyncImpl>(memory_pool);\n}\n#endif\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/io_manager.h",
    "content": "#pragma once\n\n#include <arrow/buffer.h>\n#include <arrow/result.h>\n#include <arrow/status.h>\n\n#ifdef __linux__\n#include <sys/uio.h>\n#endif\n\n#include <atomic>\n#include <cassert>\n#include <chrono>\n#include <memory>\n\nnamespace pod5 {\n\n#ifdef __linux__\nclass QueuedWrite {\npublic:\n    QueuedWrite() = default;\n\n    QueuedWrite(std::unique_ptr<arrow::ResizableBuffer> && buffer) : m_buffer(std::move(buffer)) {}\n\n    arrow::Status reset_queued_write()\n    {\n        assert(m_state != WriteState::ReadyForWrite);\n        assert(m_state != WriteState::InFlight);\n        m_iovec = {};\n        m_state = WriteState::Empty;\n        m_file_offset = -1;\n        m_file_descriptor = -1;\n        return m_buffer->Resize(0, false);\n    }\n\n    void prepare_for_write(int file_descriptor, std::uint64_t offset)\n    {\n        m_file_descriptor = file_descriptor;\n        m_file_offset = offset;\n        m_iovec = {.iov_base = m_buffer->mutable_data(), .iov_len = (std::size_t)m_buffer->size()};\n        set_state(WriteState::ReadyForWrite);\n    }\n\n    arrow::ResizableBuffer & get_buffer() { return *m_buffer; }\n\n    arrow::Buffer const & get_buffer() const { return *m_buffer; }\n\n    int file_descriptor() const { return m_file_descriptor; }\n\n    std::uint64_t file_offset() const { return m_file_offset; }\n\n    iovec * get_iovec_for_buffer() { return &m_iovec; }\n\n    enum class WriteState { Empty, ReadyForWrite, InFlight, Completed };\n\n    WriteState state() const { return m_state; }\n\n    void set_state(WriteState state) { m_state = state; }\n\nprivate:\n    std::unique_ptr<arrow::ResizableBuffer> m_buffer;\n    std::uint64_t m_file_offset{(std::uint64_t)-1};\n    iovec m_iovec{};\n    int m_file_descriptor{-1};\n    WriteState m_state{WriteState::Empty};\n};\n#endif\n\nclass IOManager {\npublic:\n    constexpr static size_t Alignment = 4096;  // buffer alignment (for block devices)\n    constexpr static size_t CachedBufferCount = 5;\n\n    virtual ~IOManager() = default;\n\n#ifdef __linux__\n    virtual arrow::Result<std::shared_ptr<QueuedWrite>> allocate_new_write(\n        std::size_t capacity) = 0;\n    virtual arrow::Status return_used_write(std::shared_ptr<QueuedWrite> && used_write) = 0;\n\n    virtual arrow::Status write_buffer(std::shared_ptr<QueuedWrite> && data) = 0;\n\n    virtual arrow::Status wait_for_event(std::chrono::nanoseconds timeout) { return {}; }\n#endif\n};\n\n#ifdef __linux__\narrow::Result<std::shared_ptr<IOManager>> make_sync_io_manager(\n    arrow::MemoryPool * memory_pool = arrow::default_memory_pool());\n#endif\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/memory_pool.cpp",
    "content": "#include \"memory_pool.h\"\n\n#ifdef _WIN32\n#include <windows.h>\n#elif !defined(__FreeBSD__)\n#include <unistd.h>\n#endif\n\nnamespace {\n\n// Referenced from the jemalloc source:\n// https://github.com/jemalloc/jemalloc/blob/b82333fdec6e5833f88780fcf1fc50b799268e1b/src/pages.c#L596C1-L616C2\nsize_t os_page_detect(void)\n{\n#ifdef _WIN32\n    SYSTEM_INFO si;\n    GetSystemInfo(&si);\n    return si.dwPageSize;\n#elif defined(__FreeBSD__)\n    /*\n\t * This returns the value obtained from\n\t * the auxv vector, avoiding a syscall.\n\t */\n    return getpagesize();\n#else\n    long result = sysconf(_SC_PAGESIZE);\n    if (result == -1) {\n        return 4095 * 16;  // Default to safe, large page size\n    }\n    return (size_t)result;\n#endif\n}\n\n}  // namespace\n\nnamespace pod5 {\n\narrow::MemoryPool * default_memory_pool()\n{\n    // Default to system memory pool for systems with large pages:\n    if (os_page_detect() > 4096) {\n        return arrow::system_memory_pool();\n    }\n    return arrow::default_memory_pool();\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/memory_pool.h",
    "content": "#pragma once\n#include <arrow/memory_pool.h>\n\nnamespace pod5 {\n\n/// \\brief Find a memory pool that should be used by default when opening or creating a pod5 file.\n/// \\note This function differs from the arrow equivalent by not using jemalloc on systems with large\n///       pages, which jemalloc does not support.\narrow::MemoryPool * default_memory_pool();\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/migration/migration.cpp",
    "content": "#include \"pod5_format/migration/migration.h\"\n\n#include <random>\n\nnamespace pod5 {\n\nstatic bool registered_delete_at_exit_called = false;\nstd::vector<arrow::internal::PlatformFilename> registered_delete_at_exit_paths;\n\nvoid register_delete_at_exit(arrow::internal::PlatformFilename const & path)\n{\n    registered_delete_at_exit_paths.push_back(path);\n\n    if (!registered_delete_at_exit_called) {\n        std::atexit([] {\n            std::size_t delete_failed = 0;\n            for (auto const & path : registered_delete_at_exit_paths) {\n                auto result = ::arrow::internal::DeleteDirTree(path);\n                if (!result.ok()) {\n                    delete_failed += 1;\n                }\n            }\n\n            if (delete_failed > 0) {\n                std::cerr << \"Warning: Failed to remove \" << delete_failed\n                          << \" temporary migration directories at exit.\\n\";\n            }\n        });\n        registered_delete_at_exit_called = true;\n    }\n}\n\nResult<std::unique_ptr<TemporaryDir>> MakeTmpDir(char const * suffix)\n{\n    std::default_random_engine gen(\n        static_cast<std::default_random_engine::result_type>(arrow::internal::GetRandomSeed()));\n\n    for (std::uint32_t counter = 0; counter < 5; ++counter) {\n        std::string tmp_path = std::string{\".tmp_\"} + suffix;\n\n        tmp_path += \"_\" + std::to_string(gen());\n\n        ARROW_ASSIGN_OR_RAISE(\n            auto filename, arrow::internal::PlatformFilename::FromString(tmp_path));\n        ARROW_ASSIGN_OR_RAISE(auto created, CreateDir(filename));\n        if (created) {\n            return std::make_unique<TemporaryDir>(std::move(filename));\n        }\n    }\n\n    return arrow::Status::Invalid(\"Failed to make temporary directory\");\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/migration/migration.h",
    "content": "#pragma once\n\n#include \"pod5_format/internal/combined_file_utils.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/schema_utils.h\"\n\n#include <arrow/util/io_util.h>\n\n#include <iostream>\n\nnamespace pod5 {\n\nvoid register_delete_at_exit(arrow::internal::PlatformFilename const & path);\n\nclass TemporaryDir {\npublic:\n    TemporaryDir(arrow::internal::PlatformFilename && path) : m_path(path) {}\n\n    ~TemporaryDir() { cleanup(); }\n\n    arrow::internal::PlatformFilename const & path() { return m_path; };\n\n    void cleanup()\n    {\n        if (m_path.ToString().empty()) {\n            return;\n        }\n\n        auto result = ::arrow::internal::DeleteDirTree(m_path);\n        if (!result.ok()) {\n            // Push the delete of this directory off to exit, when all open file handles should be closed.\n            register_delete_at_exit(m_path);\n        } else {\n            m_path = {};\n        }\n    }\n\nprivate:\n    arrow::internal::PlatformFilename m_path;\n};\n\nResult<std::unique_ptr<TemporaryDir>> MakeTmpDir(char const * suffix);\n\nclass MigrationResult {\npublic:\n    MigrationResult(combined_file_utils::ParsedFooter const & footer) : m_footer(footer) {}\n\n    MigrationResult(MigrationResult &&) = default;\n    MigrationResult & operator=(MigrationResult &&) = default;\n    MigrationResult(MigrationResult const &) = delete;\n    MigrationResult & operator=(MigrationResult const &) = delete;\n\n    combined_file_utils::ParsedFooter & footer() { return m_footer; }\n\n    combined_file_utils::ParsedFooter const & footer() const { return m_footer; }\n\n    void add_temp_dir(std::unique_ptr<TemporaryDir> && temp_dir)\n    {\n        m_temp_dirs.emplace_back(std::move(temp_dir));\n    }\n\nprivate:\n    // This is first so we clean it up last, after the\n    // footer and any open files it contains is destroyed.\n    std::vector<std::unique_ptr<TemporaryDir>> m_temp_dirs;\n    combined_file_utils::ParsedFooter m_footer;\n};\n\narrow::Result<MigrationResult> migrate_v0_to_v1(\n    MigrationResult && v0_input,\n    arrow::MemoryPool * pool);\narrow::Result<MigrationResult> migrate_v1_to_v2(\n    MigrationResult && v1_input,\n    arrow::MemoryPool * pool);\narrow::Result<MigrationResult> migrate_v2_to_v3(\n    MigrationResult && v2_input,\n    arrow::MemoryPool * pool);\narrow::Result<MigrationResult> migrate_v3_to_v4(\n    MigrationResult && v2_input,\n    arrow::MemoryPool * pool);\n\ninline arrow::Result<MigrationResult> migrate_if_required(\n    Version writer_version,\n    combined_file_utils::ParsedFooter const & read_footer,\n    std::shared_ptr<arrow::io::RandomAccessFile> const & source,\n    arrow::MemoryPool * pool)\n{\n    MigrationResult result{read_footer};\n\n    if (writer_version < Version(0, 0, 24)) {\n        // Added fields for read scaling\n        ARROW_ASSIGN_OR_RAISE(result, migrate_v0_to_v1(std::move(result), pool));\n    }\n\n    if (writer_version < Version(0, 0, 32)) {\n        // Added num samples field\n        ARROW_ASSIGN_OR_RAISE(result, migrate_v1_to_v2(std::move(result), pool));\n    }\n\n    if (writer_version < Version(0, 0, 38)) {\n        // Flattening fields\n        ARROW_ASSIGN_OR_RAISE(result, migrate_v2_to_v3(std::move(result), pool));\n    }\n    if (writer_version < Version(0, 3, 30)) {\n        // Flattening fields\n        ARROW_ASSIGN_OR_RAISE(result, migrate_v3_to_v4(std::move(result), pool));\n    }\n    return result;\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/migration/migration_utils.h",
    "content": "#pragma once\n\n#include \"pod5_format/internal/combined_file_utils.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/schema_metadata.h\"\n\n#include <arrow/array.h>\n#include <arrow/ipc/reader.h>\n#include <arrow/ipc/writer.h>\n#include <arrow/util/key_value_metadata.h>\n\n#include <iterator>\n\nnamespace pod5 {\n\ntemplate <typename T, typename U>\narrow::Result<std::shared_ptr<arrow::Array>>\nmake_filled_array(arrow::MemoryPool * pool, std::size_t row_count, U default_value)\n{\n    // Minimal iterator to repeat the same value N times.\n    // TODO: replace with std::views::repeat in C++23\n    struct RepeatIter {\n        // These are necessary to make |std::distance| and |std::copy| fast.\n        using iterator_category [[maybe_unused]] = std::random_access_iterator_tag;\n        using value_type = U const;\n        using difference_type = std::int64_t;\n        using pointer [[maybe_unused]] = value_type *;\n        using reference = value_type &;\n\n        std::size_t m_idx;\n        value_type m_value;\n\n        constexpr RepeatIter & operator++()\n        {\n            m_idx++;\n            return *this;\n        }\n\n        constexpr RepeatIter operator++(int)\n        {\n            RepeatIter retval = *this;\n            m_idx++;\n            return retval;\n        }\n\n        constexpr bool operator==(RepeatIter const & other) const { return m_idx == other.m_idx; }\n\n        constexpr bool operator!=(RepeatIter const & other) const { return !operator==(other); }\n\n        constexpr difference_type operator-(RepeatIter const & other) const\n        {\n            return static_cast<difference_type>(m_idx) - static_cast<difference_type>(other.m_idx);\n        }\n\n        constexpr reference operator*() const { return m_value; }\n    };\n\n    RepeatIter iter_begin{0, default_value};\n    RepeatIter iter_end{row_count, default_value};\n\n    T builder(pool);\n    ARROW_RETURN_NOT_OK(builder.AppendValues(iter_begin, iter_end));\n    return builder.Finish();\n}\n\ninline arrow::Status set_column(\n    std::shared_ptr<arrow::Schema> const & schema,\n    std::vector<std::shared_ptr<arrow::Array>> & columns,\n    char const * field_name,\n    arrow::Result<std::shared_ptr<arrow::Array>> const & array)\n{\n    auto field_index = schema->GetFieldIndex(field_name);\n    if (field_index == -1) {\n        return arrow::Status::Invalid(\"Failed to find field '\", field_name, \"' during migration.\");\n    }\n\n    if (field_index >= (std::int64_t)columns.size()) {\n        columns.resize(field_index + 1);\n    }\n\n    ARROW_ASSIGN_OR_RAISE(columns[field_index], array);\n\n    return arrow::Status::OK();\n}\n\ninline arrow::Status copy_column(\n    std::shared_ptr<arrow::Schema> const & schema_a,\n    std::vector<std::shared_ptr<arrow::Array>> & columns_a,\n    char const * field_name,\n    std::shared_ptr<arrow::Schema> const & schema_b,\n    std::vector<std::shared_ptr<arrow::Array>> & columns_b)\n{\n    auto field_index_a = schema_a->GetFieldIndex(field_name);\n    if (field_index_a == -1 || field_index_a >= (std::int64_t)columns_a.size()) {\n        return arrow::Status::Invalid(\"Failed to find field '\", field_name, \"' during migration.\");\n    }\n\n    auto source_column = columns_a[field_index_a];\n\n    auto field_index_b = schema_b->GetFieldIndex(field_name);\n    if (field_index_b >= (std::int64_t)columns_b.size()) {\n        columns_b.resize(field_index_b + 1);\n    }\n\n    columns_b[field_index_b] = source_column;\n\n    return arrow::Status::OK();\n}\n\nstruct Pod5BatchRecordReader {\n    std::shared_ptr<arrow::ipc::RecordBatchFileReader> reader;\n    std::shared_ptr<arrow::Schema> schema;\n    std::shared_ptr<arrow::KeyValueMetadata const> metadata;\n};\n\nstruct Pod5BatchRecordWriter {\n    std::shared_ptr<arrow::ipc::RecordBatchWriter> writer;\n    std::shared_ptr<arrow::Schema> schema;\n\n    arrow::Status write_batch(\n        std::size_t num_rows,\n        std::vector<std::shared_ptr<arrow::Array>> const & columns)\n    {\n        auto const record_batch = arrow::RecordBatch::Make(schema, num_rows, std::move(columns));\n        return writer->WriteRecordBatch(*record_batch);\n    }\n};\n\ninline pod5::Result<Pod5BatchRecordReader> open_record_batch_reader(\n    arrow::MemoryPool * pool,\n    combined_file_utils::ParsedFileInfo file_info)\n{\n    Pod5BatchRecordReader result;\n    ARROW_ASSIGN_OR_RAISE(auto file, open_sub_file(file_info));\n\n    arrow::ipc::IpcReadOptions read_options;\n    read_options.memory_pool = pool;\n    ARROW_ASSIGN_OR_RAISE(\n        result.reader, arrow::ipc::RecordBatchFileReader::Open(file, read_options));\n\n    result.schema = result.reader->schema();\n    result.metadata = result.schema->metadata();\n    if (!result.metadata) {\n        return Status::IOError(\"Missing metadata on read table schema\");\n    }\n\n    return result;\n}\n\ninline pod5::Result<std::shared_ptr<arrow::KeyValueMetadata const>> update_metadata(\n    std::shared_ptr<arrow::KeyValueMetadata const> original_metadata,\n    Version version_to_write)\n{\n    auto result = original_metadata->Copy();\n    // Update the reader for the new version:\n    ARROW_RETURN_NOT_OK(result->Set(\"MINKNOW:pod5_version\", version_to_write.to_string()));\n    return result;\n}\n\ninline pod5::Result<Pod5BatchRecordWriter> make_record_batch_writer(\n    arrow::MemoryPool * pool,\n    std::string path,\n    std::shared_ptr<arrow::Schema> schema,\n    std::shared_ptr<arrow::KeyValueMetadata const> metadata)\n{\n    ARROW_ASSIGN_OR_RAISE(auto file, arrow::io::FileOutputStream::Open(path, false));\n    arrow::ipc::IpcWriteOptions write_options;\n    write_options.memory_pool = pool;\n    write_options.emit_dictionary_deltas = true;\n\n    Pod5BatchRecordWriter result;\n    ARROW_ASSIGN_OR_RAISE(\n        result.writer, arrow::ipc::MakeFileWriter(file, schema, write_options, metadata));\n    result.schema = schema;\n\n    return result;\n}\n\ninline pod5::Status check_columns(\n    std::shared_ptr<arrow::Schema> const & schema,\n    std::vector<std::shared_ptr<arrow::Array>> const & columns)\n{\n    for (std::size_t i = 0; i < columns.size(); ++i) {\n        auto const & column = columns[i];\n        auto const & schema_field = schema->field(i);\n\n        if (auto list = std::dynamic_pointer_cast<arrow::ListArray>(column)) {\n            auto last_value = list->value_offset(0);\n            for (int i = 1; i <= list->length(); ++i) {\n                if (list->value_offset(i) < last_value) {\n                    return arrow::Status::Invalid(\n                        \"Field content for field `\",\n                        schema_field->name(),\n                        \"`, list offsets are invalid\"\n                        \" at row index \",\n                        i,\n                        \" (\",\n                        list->value_offset(i),\n                        \" < \",\n                        last_value,\n                        \")\");\n                }\n                last_value = list->value_offset(i);\n            }\n        } else if (auto dict = std::dynamic_pointer_cast<arrow::DictionaryArray>(column)) {\n            auto dict_values = dict->dictionary();\n            auto string_dictionary_values =\n                std::dynamic_pointer_cast<arrow::StringArray>(dict_values);\n            if (string_dictionary_values) {\n                auto const value_offsets = string_dictionary_values->value_offsets();\n                std::int64_t const value_offsets_length =\n                    value_offsets->size() / sizeof(arrow::StringArray::offset_type);\n                if (value_offsets_length != (1 + dict_values->length()))\n                {  // We expect N+1 offsets for the final element length\n                    return arrow::Status::Invalid(\n                        \"Dictionary length for field `\",\n                        schema_field->name(),\n                        \"`, dictionary length is \",\n                        dict_values->length(),\n                        \" but value offsets is length \",\n                        value_offsets_length);\n                }\n            }\n\n            auto indices = std::dynamic_pointer_cast<arrow::Int16Array>(dict->indices());\n            if (!indices) {\n                return arrow::Status::Invalid(\n                    \"Field content for field `\",\n                    schema_field->name(),\n                    \"`, dictionary indexes are missing\");\n            }\n            for (int i = 0; i < indices->length(); ++i) {\n                if (indices->Value(i) >= dict_values->length()) {\n                    return arrow::Status::Invalid(\n                        \"Field content for field `\",\n                        schema_field->name(),\n                        \"`, dictionary indexes are invalid\"\n                        \" at row index \",\n                        i,\n                        \" (\",\n                        indices->Value(i),\n                        \" >= \",\n                        dict_values->length(),\n                        \")\");\n                }\n            }\n        }\n    }\n\n    return {};\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/migration/v0_to_v1.cpp",
    "content": "#include \"pod5_format/migration/migration.h\"\n#include \"pod5_format/migration/migration_utils.h\"\n#include \"pod5_format/table_reader.h\"\n\n#include <arrow/array/builder_primitive.h>\n#include <arrow/status.h>\n#include <arrow/util/io_util.h>\n\n#include <iostream>\n\nnamespace pod5 {\n\narrow::Result<MigrationResult> migrate_v0_to_v1(\n    MigrationResult && v0_input,\n    arrow::MemoryPool * pool)\n{\n    ARROW_ASSIGN_OR_RAISE(auto temp_dir, MakeTmpDir(\"pod5_v0_v1_migration\"));\n    ARROW_ASSIGN_OR_RAISE(auto v1_reads_table_path, temp_dir->path().Join(\"reads_table.arrow\"));\n\n    {\n        ARROW_ASSIGN_OR_RAISE(\n            auto v0_reader, open_record_batch_reader(pool, v0_input.footer().reads_table));\n\n        auto v1_new_schama = arrow::schema(\n            {arrow::field(\"num_minknow_events\", arrow::uint64()),\n             arrow::field(\"tracked_scaling_scale\", arrow::float32()),\n             arrow::field(\"tracked_scaling_shift\", arrow::float32()),\n             arrow::field(\"predicted_scaling_scale\", arrow::float32()),\n             arrow::field(\"predicted_scaling_shift\", arrow::float32()),\n             arrow::field(\"num_reads_since_mux_change\", arrow::uint32()),\n             arrow::field(\"time_since_mux_change\", arrow::float32())});\n\n        ARROW_ASSIGN_OR_RAISE(\n            auto v1_schema, arrow::UnifySchemas({v0_reader.schema, v1_new_schama}));\n\n        ARROW_ASSIGN_OR_RAISE(\n            auto new_metadata, update_metadata(v0_reader.metadata, Version(0, 0, 24)));\n        ARROW_ASSIGN_OR_RAISE(\n            auto v1_writer,\n            make_record_batch_writer(\n                pool, v1_reads_table_path.ToString(), v1_schema, new_metadata));\n\n        for (std::int64_t batch_idx = 0; batch_idx < v0_reader.reader->num_record_batches();\n             ++batch_idx)\n        {\n            // Read V0 data:\n            ARROW_ASSIGN_OR_RAISE(\n                auto v0_batch, ReadRecordBatchAndValidate(*v0_reader.reader, batch_idx));\n            ARROW_RETURN_NOT_OK(v0_batch->ValidateFull());\n            auto const num_rows = v0_batch->num_rows();\n\n            if (num_rows < 0) {\n                return arrow::Status::Invalid(\"Invalid number of rows\");\n            } else if (POD5_ENABLE_FUZZERS && num_rows > 1'000'000) {\n                return arrow::Status::Invalid(\"Skipping huge sizes when fuzzing\");\n            }\n\n            // Extend with V1 data:\n            std::vector<std::shared_ptr<arrow::Array>> columns = v0_batch->columns();\n            ARROW_RETURN_NOT_OK(check_columns(v0_reader.schema, columns));\n            ARROW_RETURN_NOT_OK(set_column(\n                v1_schema,\n                columns,\n                \"num_minknow_events\",\n                make_filled_array<arrow::UInt64Builder>(pool, num_rows, 0)));\n            ARROW_RETURN_NOT_OK(set_column(\n                v1_schema,\n                columns,\n                \"tracked_scaling_scale\",\n                make_filled_array<arrow::FloatBuilder>(\n                    pool, num_rows, std::numeric_limits<float>::quiet_NaN())));\n            ARROW_RETURN_NOT_OK(set_column(\n                v1_schema,\n                columns,\n                \"tracked_scaling_shift\",\n                make_filled_array<arrow::FloatBuilder>(\n                    pool, num_rows, std::numeric_limits<float>::quiet_NaN())));\n            ARROW_RETURN_NOT_OK(set_column(\n                v1_schema,\n                columns,\n                \"predicted_scaling_scale\",\n                make_filled_array<arrow::FloatBuilder>(\n                    pool, num_rows, std::numeric_limits<float>::quiet_NaN())));\n            ARROW_RETURN_NOT_OK(set_column(\n                v1_schema,\n                columns,\n                \"predicted_scaling_shift\",\n                make_filled_array<arrow::FloatBuilder>(\n                    pool, num_rows, std::numeric_limits<float>::quiet_NaN())));\n            ARROW_RETURN_NOT_OK(set_column(\n                v1_schema,\n                columns,\n                \"num_reads_since_mux_change\",\n                make_filled_array<arrow::UInt32Builder>(pool, num_rows, 0)));\n            ARROW_RETURN_NOT_OK(set_column(\n                v1_schema,\n                columns,\n                \"time_since_mux_change\",\n                make_filled_array<arrow::FloatBuilder>(pool, num_rows, 0.0f)));\n            ARROW_RETURN_NOT_OK(v1_writer.write_batch(num_rows, std::move(columns)));\n        }\n\n        ARROW_RETURN_NOT_OK(v1_writer.writer->Close());\n    }\n\n    // Set up migrated data to point at our new table:\n    MigrationResult result = std::move(v0_input);\n    ARROW_RETURN_NOT_OK(result.footer().reads_table.from_full_file(v1_reads_table_path.ToString()));\n    result.add_temp_dir(std::move(temp_dir));\n\n    return result;\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/migration/v1_to_v2.cpp",
    "content": "#include \"pod5_format/migration/migration.h\"\n#include \"pod5_format/migration/migration_utils.h\"\n#include \"pod5_format/table_reader.h\"\n\n#include <arrow/array/builder_primitive.h>\n#include <arrow/ipc/reader.h>\n#include <arrow/ipc/writer.h>\n#include <arrow/util/io_util.h>\n\n#include <iostream>\n\nnamespace pod5 {\n\narrow::Result<std::size_t> get_num_samples(\n    std::shared_ptr<arrow::ListArray> const & signal_col,\n    std::size_t row_idx,\n    std::vector<std::shared_ptr<arrow::RecordBatch>> const & signal_batches)\n{\n    if (signal_batches.empty()) {\n        return 0;\n    }\n\n    std::size_t signal_batch_size = signal_batches[0]->num_rows();\n    std::size_t num_samples = 0;\n\n    auto values = std::dynamic_pointer_cast<arrow::UInt64Array>(signal_col->values());\n    if (!values) {\n        return arrow::Status::Invalid(\"Invalid signal column, potentially corrupt file.\");\n    }\n\n    auto offset = signal_col->value_offset(row_idx);\n    for (std::int64_t index = 0; index < signal_col->value_length(row_idx); ++index) {\n        auto const abs_index = offset + index;\n        if (abs_index < 0 || abs_index >= values->length()) {\n            return arrow::Status::Invalid(\"Invalid signal column, potentially corrupt file.\");\n        }\n\n        auto const abs_row = values->Value(abs_index);\n\n        auto const batch_idx = abs_row / signal_batch_size;\n        auto const batch_row = abs_row - (batch_idx * signal_batch_size);\n\n        if (batch_idx >= signal_batches.size()) {\n            return arrow::Status::Invalid(\n                \"Invalid signal row \", abs_row, \", cannot find signal batch \", batch_idx);\n        }\n\n        auto batch = signal_batches[batch_idx];\n\n        auto samples_column =\n            std::dynamic_pointer_cast<arrow::UInt32Array>(batch->GetColumnByName(\"samples\"));\n        if (!samples_column) {\n            return arrow::Status::Invalid(\"`samples` column is missing from file\");\n        }\n        if (batch_row >= (std::size_t)samples_column->length()) {\n            return arrow::Status::Invalid(\n                \"Invalid signal batch row \", batch_row, \", length is \", samples_column->length());\n        }\n        num_samples += samples_column->Value(batch_row);\n    }\n\n    return num_samples;\n}\n\narrow::Result<MigrationResult> migrate_v1_to_v2(\n    MigrationResult && v1_input,\n    arrow::MemoryPool * pool)\n{\n    ARROW_ASSIGN_OR_RAISE(auto temp_dir, MakeTmpDir(\"pod5_v1_v2_migration\"));\n    ARROW_ASSIGN_OR_RAISE(auto v2_reads_table_path, temp_dir->path().Join(\"reads_table.arrow\"));\n\n    {\n        ARROW_ASSIGN_OR_RAISE(\n            auto v1_reader, open_record_batch_reader(pool, v1_input.footer().reads_table));\n        ARROW_ASSIGN_OR_RAISE(\n            auto v1_signal_reader, open_record_batch_reader(pool, v1_input.footer().signal_table));\n        std::vector<std::shared_ptr<arrow::RecordBatch>> signal_batches(\n            v1_signal_reader.reader->num_record_batches());\n        for (std::size_t batch_idx = 0;\n             batch_idx < (std::size_t)v1_signal_reader.reader->num_record_batches();\n             ++batch_idx)\n        {\n            ARROW_ASSIGN_OR_RAISE(\n                signal_batches[batch_idx],\n                ReadRecordBatchAndValidate(*v1_signal_reader.reader, batch_idx));\n            ARROW_RETURN_NOT_OK(signal_batches[batch_idx]->ValidateFull());\n        }\n\n        auto v2_new_schama = arrow::schema({arrow::field(\"num_samples\", arrow::uint64())});\n        ARROW_ASSIGN_OR_RAISE(\n            auto new_metadata, update_metadata(v1_reader.metadata, Version(0, 0, 32)));\n        ARROW_ASSIGN_OR_RAISE(\n            auto v2_schema, arrow::UnifySchemas({v1_reader.schema, v2_new_schama}));\n        ARROW_ASSIGN_OR_RAISE(\n            auto v2_writer,\n            make_record_batch_writer(\n                pool, v2_reads_table_path.ToString(), v2_schema, new_metadata));\n\n        for (std::int64_t batch_idx = 0; batch_idx < v1_reader.reader->num_record_batches();\n             ++batch_idx)\n        {\n            // Read V1 data:\n            ARROW_ASSIGN_OR_RAISE(\n                auto v1_batch, ReadRecordBatchAndValidate(*v1_reader.reader, batch_idx));\n            ARROW_RETURN_NOT_OK(v1_batch->ValidateFull());\n            auto const num_rows = v1_batch->num_rows();\n\n            // Extend with V2 data:\n            std::vector<std::shared_ptr<arrow::Array>> columns = v1_batch->columns();\n\n            auto signal_column =\n                std::dynamic_pointer_cast<arrow::ListArray>(v1_batch->GetColumnByName(\"signal\"));\n            if (!signal_column) {\n                return arrow::Status::Invalid(\"`signal` column is missing from file\");\n            }\n            ARROW_RETURN_NOT_OK(signal_column->ValidateFull());\n\n            arrow::UInt64Builder num_samples_builder;\n            for (std::int64_t row = 0; row < num_rows; ++row) {\n                ARROW_ASSIGN_OR_RAISE(\n                    auto num_samples, get_num_samples(signal_column, row, signal_batches));\n                ARROW_RETURN_NOT_OK(num_samples_builder.Append(num_samples));\n            }\n            ARROW_RETURN_NOT_OK(\n                set_column(v2_schema, columns, \"num_samples\", num_samples_builder.Finish()));\n            ARROW_RETURN_NOT_OK(v2_writer.write_batch(num_rows, std::move(columns)));\n        }\n\n        ARROW_RETURN_NOT_OK(v2_writer.writer->Close());\n    }\n\n    // Set up migrated data to point at our new table:\n    MigrationResult result = std::move(v1_input);\n    ARROW_RETURN_NOT_OK(result.footer().reads_table.from_full_file(v2_reads_table_path.ToString()));\n    result.add_temp_dir(std::move(temp_dir));\n\n    return result;\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/migration/v2_to_v3.cpp",
    "content": "#include \"pod5_format/migration/migration.h\"\n#include \"pod5_format/migration/migration_utils.h\"\n#include \"pod5_format/types.h\"\n\n#include <arrow/array/builder_binary.h>\n#include <arrow/array/builder_primitive.h>\n#include <arrow/ipc/reader.h>\n#include <arrow/ipc/writer.h>\n#include <arrow/util/io_util.h>\n\n#include <unordered_map>\n\nnamespace pod5 {\n\nstruct StructRow {\n    std::int64_t dict_item_index;\n    std::shared_ptr<arrow::StructArray> data;\n};\n\nstruct StringDictBuilder {\n    arrow::Int16Builder indices;\n    arrow::StringBuilder items;\n\n    arrow::Result<std::shared_ptr<arrow::Array>> finish()\n    {\n        ARROW_ASSIGN_OR_RAISE(auto finished_indices, indices.Finish());\n        ARROW_ASSIGN_OR_RAISE(auto finished_items, items.Finish());\n\n        auto const & finished_items_val = static_cast<arrow::StringArray const &>(*finished_items);\n\n        // Re append the finished items to the now blank list\n        for (std::int64_t i = 0; i < finished_items_val.length(); ++i) {\n            ARROW_RETURN_NOT_OK(items.Append(finished_items_val.GetView(i)));\n        }\n\n        return arrow::DictionaryArray::FromArrays(finished_indices, finished_items);\n    }\n\n    std::unordered_map<std::string, std::int16_t> lookup;\n};\n\narrow::Result<StructRow> get_dict_struct(\n    std::shared_ptr<arrow::RecordBatch> const & batch,\n    std::size_t row,\n    char const * field_name)\n{\n    auto column = batch->GetColumnByName(field_name);\n    if (!column) {\n        return Status::Invalid(\"Failed to find column \", field_name);\n    }\n\n    auto dict_column = std::dynamic_pointer_cast<arrow::DictionaryArray>(column);\n    if (!dict_column) {\n        return Status::Invalid(\"Found column \", field_name, \" is not a dictionary as expected\");\n    }\n\n    auto dict_items = std::dynamic_pointer_cast<arrow::StructArray>(dict_column->dictionary());\n    if (!dict_items) {\n        return Status::Invalid(\"Dictionary column is not a struct as expected\");\n    }\n\n    return StructRow{dict_column->GetValueIndex(row), dict_items};\n}\n\ntemplate <typename ArrayType, typename Builder>\narrow::Status\nappend_struct_row(StructRow const & struct_row, char const * field_name, Builder & builder)\n{\n    auto field_array = struct_row.data->GetFieldByName(field_name);\n    if (!field_array) {\n        return Status::Invalid(\"Struct is missing \", field_name, \" field\");\n    }\n\n    auto typed_field_array = std::dynamic_pointer_cast<ArrayType>(field_array);\n    if (!typed_field_array) {\n        return Status::Invalid(field_name, \" field is the wrong type\");\n    }\n\n    if (struct_row.dict_item_index < 0 || struct_row.dict_item_index >= field_array->length()) {\n        return Status::Invalid(\"Dictionary index is out of range\");\n    }\n    return builder.Append(typed_field_array->Value(struct_row.dict_item_index));\n}\n\narrow::Status append_struct_row_to_dict(\n    StructRow const & struct_row,\n    char const * field_name,\n    StringDictBuilder & builder)\n{\n    auto field_array = struct_row.data->GetFieldByName(field_name);\n    if (!field_array) {\n        return Status::Invalid(\"Struct is missing \", field_name, \" field\");\n    }\n\n    auto typed_field_array = std::dynamic_pointer_cast<arrow::StringArray>(field_array);\n    if (!typed_field_array) {\n        return Status::Invalid(field_name, \" field is the wrong type\");\n    }\n\n    if (struct_row.dict_item_index < 0 || struct_row.dict_item_index >= field_array->length()) {\n        return Status::Invalid(\"Dictionary index is out of range\");\n    }\n\n    auto str_value = typed_field_array->GetString(struct_row.dict_item_index);\n    auto it = builder.lookup.find(str_value);\n    if (it != builder.lookup.end()) {\n        return builder.indices.Append(it->second);\n    }\n\n    auto index = builder.items.length();\n    ARROW_RETURN_NOT_OK(builder.items.Append(str_value));\n    builder.lookup[str_value] = index;\n    return builder.indices.Append(index);\n}\n\narrow::Result<MigrationResult> migrate_v2_to_v3(\n    MigrationResult && v2_input,\n    arrow::MemoryPool * pool)\n{\n    ARROW_ASSIGN_OR_RAISE(auto temp_dir, MakeTmpDir(\"pod5_v2_v3_migration\"));\n    ARROW_ASSIGN_OR_RAISE(auto v3_reads_table_path, temp_dir->path().Join(\"reads_table.arrow\"));\n    ARROW_ASSIGN_OR_RAISE(\n        auto v3_run_info_table_path, temp_dir->path().Join(\"run_info_table.arrow\"));\n\n    {\n        ARROW_ASSIGN_OR_RAISE(\n            auto v2_reader, open_record_batch_reader(pool, v2_input.footer().reads_table));\n        ARROW_ASSIGN_OR_RAISE(\n            auto new_metadata, update_metadata(v2_reader.metadata, Version(0, 0, 35)));\n\n        auto const num_record_batches = v2_reader.reader->num_record_batches();\n\n        {\n            auto v3_reads_schema = arrow::schema(\n                {arrow::field(\"read_id\", uuid()),\n                 arrow::field(\"signal\", arrow::list(arrow::uint64())),\n                 arrow::field(\"read_number\", arrow::uint32()),\n                 arrow::field(\"start\", arrow::uint64()),\n                 arrow::field(\"median_before\", arrow::float32()),\n                 arrow::field(\"num_minknow_events\", arrow::uint64()),\n                 arrow::field(\"tracked_scaling_scale\", arrow::float32()),\n                 arrow::field(\"tracked_scaling_shift\", arrow::float32()),\n                 arrow::field(\"predicted_scaling_scale\", arrow::float32()),\n                 arrow::field(\"predicted_scaling_shift\", arrow::float32()),\n                 arrow::field(\"num_reads_since_mux_change\", arrow::uint32()),\n                 arrow::field(\"time_since_mux_change\", arrow::float32()),\n                 arrow::field(\"num_samples\", arrow::uint64()),\n                 arrow::field(\"channel\", arrow::uint16()),\n                 arrow::field(\"well\", arrow::uint8()),\n                 arrow::field(\"pore_type\", arrow::dictionary(arrow::int16(), arrow::utf8())),\n                 arrow::field(\"calibration_offset\", arrow::float32()),\n                 arrow::field(\"calibration_scale\", arrow::float32()),\n                 arrow::field(\"end_reason\", arrow::dictionary(arrow::int16(), arrow::utf8())),\n                 arrow::field(\"end_reason_forced\", arrow::boolean()),\n                 arrow::field(\"run_info\", arrow::dictionary(arrow::int16(), arrow::utf8()))},\n                new_metadata);\n            ARROW_ASSIGN_OR_RAISE(\n                auto v3_reads_writer,\n                make_record_batch_writer(\n                    pool, v3_reads_table_path.ToString(), v3_reads_schema, new_metadata));\n\n            std::vector<std::string> const columns_to_copy{\n                \"read_id\",\n                \"signal\",\n                \"read_number\",\n                \"start\",\n                \"median_before\",\n                \"num_minknow_events\",\n                \"tracked_scaling_scale\",\n                \"tracked_scaling_shift\",\n                \"predicted_scaling_scale\",\n                \"predicted_scaling_shift\",\n                \"num_reads_since_mux_change\",\n                \"time_since_mux_change\",\n                \"num_samples\"};\n\n            // Builders for dict columns\n            StringDictBuilder pore_type;\n            StringDictBuilder end_reason;\n            StringDictBuilder run_info;\n            for (std::int64_t batch_idx = 0; batch_idx < num_record_batches; ++batch_idx) {\n                // Read V2 data:\n                ARROW_ASSIGN_OR_RAISE(auto v2_batch, v2_reader.reader->ReadRecordBatch(batch_idx));\n                ARROW_RETURN_NOT_OK(v2_batch->ValidateFull());\n                auto const num_rows = v2_batch->num_rows();\n\n                std::vector<std::shared_ptr<arrow::Array>> v3_columns;\n\n                // Write V3 data:\n                std::vector<std::shared_ptr<arrow::Array>> v2_columns = v2_batch->columns();\n                for (auto const & col_name : columns_to_copy) {\n                    ARROW_RETURN_NOT_OK(copy_column(\n                        v2_reader.schema,\n                        v2_columns,\n                        col_name.data(),\n                        v3_reads_schema,\n                        v3_columns));\n                }\n\n                arrow::UInt16Builder channel;\n                arrow::UInt8Builder well;\n                arrow::FloatBuilder calibration_offset;\n                arrow::FloatBuilder calibration_scale;\n                arrow::BooleanBuilder end_reason_forced;\n                for (std::int64_t row = 0; row < num_rows; ++row) {\n                    ARROW_ASSIGN_OR_RAISE(\n                        auto calibration_data, get_dict_struct(v2_batch, row, \"calibration\"));\n                    ARROW_RETURN_NOT_OK(\n                        append_struct_row<arrow::FloatArray>(\n                            calibration_data, \"offset\", calibration_offset));\n                    ARROW_RETURN_NOT_OK(\n                        append_struct_row<arrow::FloatArray>(\n                            calibration_data, \"scale\", calibration_scale));\n\n                    ARROW_ASSIGN_OR_RAISE(auto pore_data, get_dict_struct(v2_batch, row, \"pore\"));\n                    ARROW_RETURN_NOT_OK(\n                        append_struct_row<arrow::UInt16Array>(pore_data, \"channel\", channel));\n                    ARROW_RETURN_NOT_OK(\n                        append_struct_row<arrow::UInt8Array>(pore_data, \"well\", well));\n                    ARROW_RETURN_NOT_OK(\n                        append_struct_row_to_dict(pore_data, \"pore_type\", pore_type));\n\n                    ARROW_ASSIGN_OR_RAISE(\n                        auto end_reason_data, get_dict_struct(v2_batch, row, \"end_reason\"));\n                    ARROW_RETURN_NOT_OK(\n                        append_struct_row_to_dict(end_reason_data, \"name\", end_reason));\n                    ARROW_RETURN_NOT_OK(\n                        append_struct_row<arrow::BooleanArray>(\n                            end_reason_data, \"forced\", end_reason_forced));\n\n                    ARROW_ASSIGN_OR_RAISE(\n                        auto run_info_data, get_dict_struct(v2_batch, row, \"run_info\"));\n                    ARROW_RETURN_NOT_OK(\n                        append_struct_row_to_dict(run_info_data, \"acquisition_id\", run_info));\n                }\n                ARROW_RETURN_NOT_OK(set_column(\n                    v3_reads_schema,\n                    v3_columns,\n                    \"calibration_offset\",\n                    calibration_offset.Finish()));\n                ARROW_RETURN_NOT_OK(set_column(\n                    v3_reads_schema, v3_columns, \"calibration_scale\", calibration_scale.Finish()));\n                ARROW_RETURN_NOT_OK(\n                    set_column(v3_reads_schema, v3_columns, \"channel\", channel.Finish()));\n                ARROW_RETURN_NOT_OK(set_column(v3_reads_schema, v3_columns, \"well\", well.Finish()));\n                ARROW_RETURN_NOT_OK(\n                    set_column(v3_reads_schema, v3_columns, \"pore_type\", pore_type.finish()));\n                ARROW_RETURN_NOT_OK(\n                    set_column(v3_reads_schema, v3_columns, \"end_reason\", end_reason.finish()));\n                ARROW_RETURN_NOT_OK(set_column(\n                    v3_reads_schema, v3_columns, \"end_reason_forced\", end_reason_forced.Finish()));\n                ARROW_RETURN_NOT_OK(\n                    set_column(v3_reads_schema, v3_columns, \"run_info\", run_info.finish()));\n\n                ARROW_RETURN_NOT_OK(v3_reads_writer.write_batch(num_rows, std::move(v3_columns)));\n            }\n            ARROW_RETURN_NOT_OK(v3_reads_writer.writer->Close());\n        }\n\n        if (num_record_batches > 0) {\n            ARROW_ASSIGN_OR_RAISE(\n                auto v2_last_batch, v2_reader.reader->ReadRecordBatch(num_record_batches - 1));\n            auto run_info_column = std::dynamic_pointer_cast<arrow::DictionaryArray>(\n                v2_last_batch->GetColumnByName(\"run_info\"));\n            if (!run_info_column) {\n                return arrow::Status::Invalid(\"Failed to find the run info column\");\n            }\n            auto run_info_dict_type =\n                std::dynamic_pointer_cast<arrow::DictionaryType>(run_info_column->type());\n            if (!run_info_dict_type) {\n                return arrow::Status::Invalid(\"Failed to find a run info of the right type\");\n            }\n            auto run_info_items =\n                std::dynamic_pointer_cast<arrow::StructArray>(run_info_column->dictionary());\n            if (!run_info_items) {\n                return arrow::Status::Invalid(\"Failed to find a run info items array\");\n            }\n            auto run_info_items_type =\n                std::dynamic_pointer_cast<arrow::StructType>(run_info_items->type());\n            if (!run_info_items_type) {\n                return arrow::Status::Invalid(\n                    \"Failed to find a run info items array of the right type\");\n            }\n\n            // Append all the run info dict-struct data to the new table:\n            auto v3_run_info_schema = arrow::schema(run_info_items_type->fields(), new_metadata);\n            ARROW_ASSIGN_OR_RAISE(\n                auto v3_run_info_writer,\n                make_record_batch_writer(\n                    pool, v3_run_info_table_path.ToString(), v3_run_info_schema, new_metadata));\n\n            auto const & fields = run_info_items->fields();\n            std::vector<std::shared_ptr<arrow::Array>> v3_columns(\n                v3_run_info_schema->fields().size());\n            for (std::size_t col = 0; col < v3_columns.size(); ++col) {\n                v3_columns[col] = fields[col];\n            }\n\n            ARROW_RETURN_NOT_OK(\n                v3_run_info_writer.write_batch(run_info_items->length(), std::move(v3_columns)));\n            ARROW_RETURN_NOT_OK(v3_run_info_writer.writer->Close());\n        }\n    }\n\n    // Set up migrated data to point at our new table:\n    MigrationResult result = std::move(v2_input);\n    ARROW_RETURN_NOT_OK(result.footer().reads_table.from_full_file(v3_reads_table_path.ToString()));\n    ARROW_RETURN_NOT_OK(\n        result.footer().run_info_table.from_full_file(v3_run_info_table_path.ToString()));\n    result.add_temp_dir(std::move(temp_dir));\n\n    return result;\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/migration/v3_to_v4.cpp",
    "content": "#include \"pod5_format/migration/migration.h\"\n#include \"pod5_format/migration/migration_utils.h\"\n#include \"pod5_format/table_reader.h\"\n\n#include <arrow/array/builder_primitive.h>\n#include <arrow/status.h>\n#include <arrow/util/io_util.h>\n\n#include <unordered_map>\n\nnamespace pod5 {\n\narrow::Result<MigrationResult> migrate_v3_to_v4(\n    MigrationResult && v3_input,\n    arrow::MemoryPool * pool)\n{\n    ARROW_ASSIGN_OR_RAISE(auto temp_dir, MakeTmpDir(\"pod5_v3_v4_migration\"));\n    ARROW_ASSIGN_OR_RAISE(auto v4_reads_table_path, temp_dir->path().Join(\"reads_table.arrow\"));\n\n    {\n        ARROW_ASSIGN_OR_RAISE(\n            auto v3_reader, open_record_batch_reader(pool, v3_input.footer().reads_table));\n\n        auto v4_new_schama = arrow::schema({arrow::field(\"open_pore_level\", arrow::float32())});\n\n        ARROW_ASSIGN_OR_RAISE(\n            auto v4_schema, arrow::UnifySchemas({v3_reader.schema, v4_new_schama}));\n\n        ARROW_ASSIGN_OR_RAISE(\n            auto new_metadata, update_metadata(v3_reader.metadata, Version(0, 3, 30)));\n        ARROW_ASSIGN_OR_RAISE(\n            auto v4_writer,\n            make_record_batch_writer(\n                pool, v4_reads_table_path.ToString(), v4_schema, new_metadata));\n\n        for (std::int64_t batch_idx = 0; batch_idx < v3_reader.reader->num_record_batches();\n             ++batch_idx)\n        {\n            // Read V0 data:\n            ARROW_ASSIGN_OR_RAISE(\n                auto v3_batch, ReadRecordBatchAndValidate(*v3_reader.reader, batch_idx));\n            ARROW_RETURN_NOT_OK(v3_batch->ValidateFull());\n            auto const num_rows = v3_batch->num_rows();\n\n            if (num_rows < 0) {\n                return arrow::Status::Invalid(\"Invalid number of rows\");\n            } else if (POD5_ENABLE_FUZZERS && num_rows > 1'000'000) {\n                return arrow::Status::Invalid(\"Skipping huge sizes when fuzzing\");\n            }\n\n            // Extend with V4 data:\n            std::vector<std::shared_ptr<arrow::Array>> columns = v3_batch->columns();\n            ARROW_RETURN_NOT_OK(check_columns(v3_reader.schema, columns));\n            ARROW_RETURN_NOT_OK(set_column(\n                v4_schema,\n                columns,\n                \"open_pore_level\",\n                make_filled_array<arrow::FloatBuilder>(\n                    pool, num_rows, std::numeric_limits<float>::quiet_NaN())));\n            ARROW_RETURN_NOT_OK(v4_writer.write_batch(num_rows, std::move(columns)));\n        }\n\n        ARROW_RETURN_NOT_OK(v4_writer.writer->Close());\n    }\n\n    // Set up migrated data to point at our new table:\n    MigrationResult result = std::move(v3_input);\n    ARROW_RETURN_NOT_OK(result.footer().reads_table.from_full_file(v4_reads_table_path.ToString()));\n    result.add_temp_dir(std::move(temp_dir));\n\n    return result;\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/read_table_reader.cpp",
    "content": "#include \"pod5_format/read_table_reader.h\"\n\n#include \"pod5_format/read_table_utils.h\"\n#include \"pod5_format/schema_metadata.h\"\n#include \"pod5_format/schema_utils.h\"\n\n#include <arrow/array/array_binary.h>\n#include <arrow/array/array_dict.h>\n#include <arrow/array/array_nested.h>\n#include <arrow/array/array_primitive.h>\n#include <arrow/ipc/reader.h>\n\n#include <algorithm>\n\nnamespace pod5 {\n\nReadTableRecordBatch::ReadTableRecordBatch(\n    std::shared_ptr<arrow::RecordBatch> && batch,\n    std::shared_ptr<ReadTableSchemaDescription const> const & field_locations)\n: TableRecordBatch(std::move(batch))\n, m_field_locations(field_locations)\n{\n}\n\nReadTableRecordBatch::ReadTableRecordBatch(ReadTableRecordBatch && other)\n: TableRecordBatch(std::move(other))\n, m_field_locations(std::move(other.m_field_locations))\n{\n}\n\nstd::shared_ptr<UuidArray> ReadTableRecordBatch::read_id_column() const\n{\n    return find_column(batch(), m_field_locations->read_id);\n}\n\nstd::shared_ptr<arrow::ListArray> ReadTableRecordBatch::signal_column() const\n{\n    return find_column(batch(), m_field_locations->signal);\n}\n\nResult<ReadTableRecordColumns> ReadTableRecordBatch::columns() const\n{\n    ReadTableRecordColumns result;\n    result.table_version = m_field_locations->table_version();\n\n    auto const & bat = batch();\n\n    // V0 fields:\n    result.read_id = find_column(bat, m_field_locations->read_id);\n    result.signal = find_column(bat, m_field_locations->signal);\n    result.read_number = find_column(bat, m_field_locations->read_number);\n    result.start_sample = find_column(bat, m_field_locations->start);\n    result.median_before = find_column(bat, m_field_locations->median_before);\n\n    // V1 fields:\n    if (result.table_version >= ReadTableSpecVersion::v1()) {\n        result.num_minknow_events = find_column(bat, m_field_locations->num_minknow_events);\n\n        result.tracked_scaling_scale = find_column(bat, m_field_locations->tracked_scaling_scale);\n        result.tracked_scaling_shift = find_column(bat, m_field_locations->tracked_scaling_shift);\n        result.predicted_scaling_scale =\n            find_column(bat, m_field_locations->predicted_scaling_scale);\n        result.predicted_scaling_shift =\n            find_column(bat, m_field_locations->predicted_scaling_shift);\n        result.num_reads_since_mux_change =\n            find_column(bat, m_field_locations->num_reads_since_mux_change);\n        result.time_since_mux_change = find_column(bat, m_field_locations->time_since_mux_change);\n    }\n\n    // V2 fields:\n    if (result.table_version >= ReadTableSpecVersion::v2()) {\n        result.num_samples = find_column(bat, m_field_locations->num_samples);\n    }\n\n    // V3 fields:\n    if (result.table_version >= ReadTableSpecVersion::v3()) {\n        result.channel = find_column(bat, m_field_locations->channel);\n        result.well = find_column(bat, m_field_locations->well);\n        result.pore_type = find_column(bat, m_field_locations->pore_type);\n        result.calibration_offset = find_column(bat, m_field_locations->calibration_offset);\n        result.calibration_scale = find_column(bat, m_field_locations->calibration_scale);\n        result.end_reason = find_column(bat, m_field_locations->end_reason);\n        result.end_reason_forced = find_column(bat, m_field_locations->end_reason_forced);\n        result.run_info = find_column(bat, m_field_locations->run_info);\n    }\n\n    if (result.table_version >= ReadTableSpecVersion::v4()) {\n        result.open_pore_level = find_column(bat, m_field_locations->open_pore_level);\n    }\n\n    return result;\n}\n\nResult<std::shared_ptr<arrow::UInt64Array>> ReadTableRecordBatch::get_signal_rows(\n    std::int64_t batch_row) const\n{\n    auto signal_col = signal_column();\n\n    auto const & values = signal_col->values();\n\n    auto const offset = signal_col->value_offset(batch_row);\n    if (offset >= values->length()) {\n        return arrow::Status::Invalid(\n            \"Invalid signal row offset '\", offset, \"' is outside the size of the values array.\");\n    }\n\n    auto const length = signal_col->value_length(batch_row);\n    if (length > values->length() - offset) {\n        return arrow::Status::Invalid(\n            \"Invalid signal row length '\", length, \"' is outside the size of the values array.\");\n    }\n\n    return std::static_pointer_cast<arrow::UInt64Array>(values->Slice(offset, length));\n}\n\ntemplate <ReadTableRecordBatch::Dict which>\nauto & ReadTableRecordBatch::get_dictionary(\n    std::shared_ptr<arrow::DictionaryArray> const & array) const\n{\n    auto & initialised = m_dictionary_initialised[static_cast<std::size_t>(which)];\n    if (initialised.load(std::memory_order_acquire)) {\n        return array->dictionary();\n    }\n\n    std::lock_guard lock(m_dictionary_access_lock);\n    auto & dict = array->dictionary();\n    initialised.store(true, std::memory_order_release);\n    return dict;\n}\n\nResult<std::string> ReadTableRecordBatch::get_pore_type(std::int16_t pore_index) const\n{\n    if (!m_field_locations->pore_type.found_field()) {\n        return arrow::Status::Invalid(\"pore field is not present in the file\");\n    }\n\n    auto pore_column = find_column(batch(), m_field_locations->pore_type);\n    auto const & pore_dict = get_dictionary<Dict::Pore>(pore_column);\n    auto const & pore_data = static_cast<arrow::StringArray const &>(*pore_dict);\n    if (pore_index < 0 || pore_index >= pore_data.length()) {\n        return arrow::Status::IndexError(\n            \"Invalid index \", pore_index, \" for pore array of length \", pore_data.length());\n    }\n\n    return pore_data.GetString(pore_index);\n}\n\nResult<std::pair<ReadEndReason, std::string>> ReadTableRecordBatch::get_end_reason(\n    std::int16_t end_reason_index) const\n{\n    if (!m_field_locations->end_reason.found_field()) {\n        return arrow::Status::Invalid(\"end_reason field is not present in the file\");\n    }\n\n    auto end_reason_column = find_column(batch(), m_field_locations->end_reason);\n    auto const & end_reason_dict = get_dictionary<Dict::EndReason>(end_reason_column);\n    auto const & end_reason_data = static_cast<arrow::StringArray const &>(*end_reason_dict);\n    if (end_reason_index >= end_reason_data.length()) {\n        return arrow::Status::IndexError(\n            \"Invalid index \",\n            end_reason_index,\n            \" for end reason array of length \",\n            end_reason_data.length());\n    }\n\n    auto str_value = end_reason_data.GetString(end_reason_index);\n    auto reason = end_reason_from_string(str_value);\n\n    return std::make_pair(reason, std::move(str_value));\n}\n\nResult<std::string> ReadTableRecordBatch::get_run_info(std::int16_t run_info_index) const\n{\n    if (!m_field_locations->run_info.found_field()) {\n        return arrow::Status::Invalid(\"end_reason field is not present in the file\");\n    }\n\n    auto run_info_column = find_column(batch(), m_field_locations->run_info);\n    auto const & run_info_dict = get_dictionary<Dict::RunInfo>(run_info_column);\n    auto const & run_info_data = static_cast<arrow::StringArray const &>(*run_info_dict);\n    if (run_info_index < 0 || run_info_index >= run_info_data.length()) {\n        return arrow::Status::IndexError(\n            \"Invalid index \",\n            run_info_index,\n            \" for run info array of length \",\n            run_info_data.length());\n    }\n\n    return run_info_data.GetString(run_info_index);\n}\n\n//---------------------------------------------------------------------------------------------------------------------\n\nReadTableReader::ReadTableReader(\n    std::shared_ptr<void> && input_source,\n    std::shared_ptr<arrow::ipc::RecordBatchFileReader> && reader,\n    std::shared_ptr<ReadTableSchemaDescription const> const & field_locations,\n    SchemaMetadataDescription && schema_metadata,\n    arrow::MemoryPool * pool)\n: TableReader(std::move(input_source), std::move(reader), std::move(schema_metadata), pool)\n, m_field_locations(field_locations)\n{\n}\n\nReadTableReader::ReadTableReader(ReadTableReader && other)\n: TableReader(std::move(other))\n, m_sorted_file_read_ids(std::move(other.m_sorted_file_read_ids))\n, m_field_locations(std::move(other.m_field_locations))\n{\n}\n\nReadTableReader & ReadTableReader::operator=(ReadTableReader && other)\n{\n    static_cast<TableReader &>(*this) = std::move(static_cast<TableReader &>(*this));\n    m_field_locations = std::move(other.m_field_locations);\n    m_sorted_file_read_ids = std::move(other.m_sorted_file_read_ids);\n    return *this;\n}\n\nResult<ReadTableRecordBatch> ReadTableReader::read_record_batch(std::size_t i) const\n{\n    std::lock_guard<std::mutex> l(m_batch_get_mutex);\n    ARROW_ASSIGN_OR_RAISE(auto record_batch, TableReader::ReadRecordBatch(i));\n    return ReadTableRecordBatch{std::move(record_batch), m_field_locations};\n}\n\nStatus ReadTableReader::build_read_id_lookup() const\n{\n    std::lock_guard lock(m_sorted_file_read_ids_mutex);\n\n    if (!m_sorted_file_read_ids.empty()) {\n        return Status::OK();\n    }\n\n    std::vector<IndexData> file_read_ids;\n\n    auto const batch_count = num_record_batches();\n    std::size_t abs_row_count = 0;\n\n    // Loop each batch and copy read ids out into the index:\n    for (std::size_t i = 0; i < batch_count; ++i) {\n        ARROW_ASSIGN_OR_RAISE(auto batch, read_record_batch(i));\n\n        if (file_read_ids.empty()) {\n            file_read_ids.reserve(batch.num_rows() * batch_count);\n        }\n        file_read_ids.resize(file_read_ids.size() + batch.num_rows());\n\n        auto read_id_col = batch.read_id_column();\n        auto raw_read_id_values = read_id_col->raw_values();\n        for (std::size_t row = 0; row < (std::size_t)read_id_col->length(); ++row) {\n            // Record the id, and its location within the file:\n            file_read_ids[abs_row_count].id = raw_read_id_values[row];\n            file_read_ids[abs_row_count].batch = i;\n            file_read_ids[abs_row_count].batch_row = row;\n            abs_row_count += 1;\n        }\n    }\n\n    // Sort by read id for searching later:\n    std::sort(file_read_ids.begin(), file_read_ids.end(), [](auto const & a, auto const & b) {\n        return a.id < b.id;\n    });\n\n    // Move data out now we successfully build the index:\n    m_sorted_file_read_ids = std::move(file_read_ids);\n\n    return Status::OK();\n}\n\nResult<std::size_t> ReadTableReader::search_for_read_ids(\n    ReadIdSearchInput const & search_input,\n    gsl::span<uint32_t> const & batch_counts,\n    gsl::span<uint32_t> const & batch_rows) const\n{\n    ARROW_RETURN_NOT_OK(build_read_id_lookup());\n\n    if (m_sorted_file_read_ids.empty()) {\n        return 0;\n    }\n\n    std::size_t successes = 0;\n\n    std::vector<std::vector<std::uint32_t>> batch_data(batch_counts.size());\n    auto const initial_reserve_size = search_input.read_id_count() / batch_counts.size();\n    for (auto & br : batch_data) {\n        br.reserve(initial_reserve_size);\n    }\n\n    auto file_ids_current_it = m_sorted_file_read_ids.begin();\n    auto const file_ids_end = m_sorted_file_read_ids.end();\n    for (std::size_t i = 0; i < search_input.read_id_count(); ++i) {\n        auto const & search_item = search_input[i];\n\n        // Increment file pointer while less than the search term:\n        while (file_ids_current_it != file_ids_end && file_ids_current_it->id < search_item.id) {\n            ++file_ids_current_it;\n        }\n\n        // No more ids to search, both lists are sorted and we haven't found this one, we won't find any others.\n        if (file_ids_current_it == file_ids_end) {\n            break;\n        }\n\n        // If we found it record the location:\n        if (file_ids_current_it->id == search_item.id) {\n            batch_data[file_ids_current_it->batch].push_back(file_ids_current_it->batch_row);\n            successes += 1;\n        }\n    }\n\n    std::size_t full_size_so_far = 0;\n    for (std::size_t i = 0; i < batch_data.size(); ++i) {\n        auto & data = batch_data[i];\n        batch_counts[i] = data.size();\n\n        // Ensure the batch indices within the batch are sorted:\n        std::sort(data.begin(), data.end());\n\n        // Copy the row indices into the packed vector:\n        std::copy(data.begin(), data.end(), batch_rows.begin() + full_size_so_far);\n\n        full_size_so_far += data.size();\n    }\n\n    return successes;\n}\n\n//---------------------------------------------------------------------------------------------------------------------\n\nResult<ReadTableReader> make_read_table_reader(\n    std::shared_ptr<arrow::io::RandomAccessFile> const & input,\n    arrow::MemoryPool * pool)\n{\n    arrow::ipc::IpcReadOptions options;\n    options.memory_pool = pool;\n\n    ARROW_ASSIGN_OR_RAISE(auto reader, arrow::ipc::RecordBatchFileReader::Open(input, options));\n\n    auto read_metadata_key_values = reader->schema()->metadata();\n    if (!read_metadata_key_values) {\n        return Status::IOError(\"Missing metadata on read table schema\");\n    }\n    ARROW_ASSIGN_OR_RAISE(\n        auto read_metadata, read_schema_key_value_metadata(read_metadata_key_values));\n    ARROW_ASSIGN_OR_RAISE(\n        auto field_locations, read_read_table_schema(read_metadata, reader->schema()));\n\n    return ReadTableReader(\n        {input}, std::move(reader), field_locations, std::move(read_metadata), pool);\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/read_table_reader.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/read_table_schema.h\"\n#include \"pod5_format/read_table_utils.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/schema_metadata.h\"\n#include \"pod5_format/table_reader.h\"\n#include \"pod5_format/types.h\"\n#include \"pod5_format/uuid.h\"\n\n#include <arrow/io/type_fwd.h>\n#include <gsl/gsl-lite.hpp>\n\n#include <mutex>\n\nnamespace arrow {\nclass Schema;\n\nnamespace io {\nclass RandomAccessFile;\n}\n\nnamespace ipc {\nclass RecordBatchFileReader;\n}\n}  // namespace arrow\n\nnamespace pod5 {\n\nclass CalibrationData;\nclass EndReasonData;\nclass PoreData;\nclass RunInfoData;\nclass ReadIdSearchInput;\n\nstruct ReadTableRecordColumns {\n    std::shared_ptr<UuidArray> read_id;\n    std::shared_ptr<arrow::ListArray> signal;\n    std::shared_ptr<arrow::UInt32Array> read_number;\n    std::shared_ptr<arrow::UInt64Array> start_sample;\n    std::shared_ptr<arrow::FloatArray> median_before;\n\n    std::shared_ptr<arrow::UInt64Array> num_minknow_events;\n\n    [[deprecated]] std::shared_ptr<arrow::FloatArray> tracked_scaling_scale;\n    [[deprecated]] std::shared_ptr<arrow::FloatArray> tracked_scaling_shift;\n    [[deprecated]] std::shared_ptr<arrow::FloatArray> predicted_scaling_scale;\n    [[deprecated]] std::shared_ptr<arrow::FloatArray> predicted_scaling_shift;\n    [[deprecated]] std::shared_ptr<arrow::UInt32Array> num_reads_since_mux_change;\n    [[deprecated]] std::shared_ptr<arrow::FloatArray> time_since_mux_change;\n\n    std::shared_ptr<arrow::UInt64Array> num_samples;\n\n    std::shared_ptr<arrow::UInt16Array> channel;\n    std::shared_ptr<arrow::UInt8Array> well;\n    std::shared_ptr<arrow::DictionaryArray> pore_type;\n    std::shared_ptr<arrow::FloatArray> calibration_offset;\n    std::shared_ptr<arrow::FloatArray> calibration_scale;\n    std::shared_ptr<arrow::DictionaryArray> end_reason;\n    std::shared_ptr<arrow::BooleanArray> end_reason_forced;\n    std::shared_ptr<arrow::DictionaryArray> run_info;\n\n    std::shared_ptr<arrow::FloatArray> open_pore_level;\n\n    TableSpecVersion table_version;\n};\n\nclass POD5_FORMAT_EXPORT ReadTableRecordBatch : public TableRecordBatch {\npublic:\n    ReadTableRecordBatch(\n        std::shared_ptr<arrow::RecordBatch> && batch,\n        std::shared_ptr<ReadTableSchemaDescription const> const & field_locations);\n\n    ReadTableRecordBatch(ReadTableRecordBatch &&);\n\n    std::shared_ptr<UuidArray> read_id_column() const;\n    std::shared_ptr<arrow::ListArray> signal_column() const;\n\n    Result<std::string> get_pore_type(std::int16_t pore_dict_index) const;\n    Result<std::pair<ReadEndReason, std::string>> get_end_reason(\n        std::int16_t end_reason_dict_index) const;\n    Result<std::string> get_run_info(std::int16_t run_info_dict_index) const;\n\n    Result<ReadTableRecordColumns> columns() const;\n\n    Result<std::shared_ptr<arrow::UInt64Array>> get_signal_rows(std::int64_t batch_row) const;\n\nprivate:\n    std::shared_ptr<ReadTableSchemaDescription const> m_field_locations;\n\n    enum class Dict : std::size_t { Pore, EndReason, RunInfo, Max };\n    mutable std::atomic_bool m_dictionary_initialised[static_cast<std::size_t>(Dict::Max)]{};\n    mutable std::mutex m_dictionary_access_lock;\n    template <Dict which>\n    auto & get_dictionary(std::shared_ptr<arrow::DictionaryArray> const & array) const;\n};\n\nclass POD5_FORMAT_EXPORT ReadTableReader : public TableReader {\npublic:\n    ReadTableReader(\n        std::shared_ptr<void> && input_source,\n        std::shared_ptr<arrow::ipc::RecordBatchFileReader> && reader,\n        std::shared_ptr<ReadTableSchemaDescription const> const & field_locations,\n        SchemaMetadataDescription && schema_metadata,\n        arrow::MemoryPool * pool);\n\n    ReadTableReader(ReadTableReader && other);\n    ReadTableReader & operator=(ReadTableReader && other);\n\n    Result<ReadTableRecordBatch> read_record_batch(std::size_t i) const;\n\n    Result<std::size_t> search_for_read_ids(\n        ReadIdSearchInput const & search_input,\n        gsl::span<uint32_t> const & batch_counts,\n        gsl::span<uint32_t> const & batch_rows) const;\n\nprivate:\n    struct IndexData {\n        Uuid id;\n        std::size_t batch;\n        std::size_t batch_row;\n    };\n\n    Status build_read_id_lookup() const;\n    mutable std::vector<IndexData> m_sorted_file_read_ids;\n    mutable std::mutex m_sorted_file_read_ids_mutex;\n\nprivate:\n    std::shared_ptr<ReadTableSchemaDescription const> m_field_locations;\n\n    mutable std::mutex m_batch_get_mutex;\n};\n\nPOD5_FORMAT_EXPORT Result<ReadTableReader> make_read_table_reader(\n    std::shared_ptr<arrow::io::RandomAccessFile> const & sink,\n    arrow::MemoryPool * pool);\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/read_table_schema.cpp",
    "content": "#include \"pod5_format/read_table_schema.h\"\n\n#include \"pod5_format/schema_metadata.h\"\n#include \"pod5_format/types.h\"\n\nnamespace pod5 {\n\nReadTableSchemaDescription::ReadTableSchemaDescription()\n: SchemaDescriptionBase(ReadTableSpecVersion::latest())\n// V0 Fields\n, read_id(this, \"read_id\", uuid(), ReadTableSpecVersion::v0())\n, signal(this, \"signal\", arrow::list(arrow::uint64()), ReadTableSpecVersion::v0())\n, read_number(this, \"read_number\", arrow::uint32(), ReadTableSpecVersion::v0())\n, start(this, \"start\", arrow::uint64(), ReadTableSpecVersion::v0())\n, median_before(this, \"median_before\", arrow::float32(), ReadTableSpecVersion::v0())\n,\n// V1 Fields\nnum_minknow_events(this, \"num_minknow_events\", arrow::uint64(), ReadTableSpecVersion::v1())\n, tracked_scaling_scale(this, \"tracked_scaling_scale\", arrow::float32(), ReadTableSpecVersion::v1())\n, tracked_scaling_shift(this, \"tracked_scaling_shift\", arrow::float32(), ReadTableSpecVersion::v1())\n, predicted_scaling_scale(\n      this,\n      \"predicted_scaling_scale\",\n      arrow::float32(),\n      ReadTableSpecVersion::v1())\n, predicted_scaling_shift(\n      this,\n      \"predicted_scaling_shift\",\n      arrow::float32(),\n      ReadTableSpecVersion::v1())\n, num_reads_since_mux_change(\n      this,\n      \"num_reads_since_mux_change\",\n      arrow::uint32(),\n      ReadTableSpecVersion::v1())\n, time_since_mux_change(this, \"time_since_mux_change\", arrow::float32(), ReadTableSpecVersion::v1())\n,\n// V2 Fields\nnum_samples(this, \"num_samples\", arrow::uint64(), ReadTableSpecVersion::v2())\n,\n// V3 Fields\nchannel(this, \"channel\", arrow::uint16(), ReadTableSpecVersion::v3())\n, well(this, \"well\", arrow::uint8(), ReadTableSpecVersion::v3())\n, pore_type(\n      this,\n      \"pore_type\",\n      arrow::dictionary(arrow::int16(), arrow::utf8()),\n      ReadTableSpecVersion::v3())\n, calibration_offset(this, \"calibration_offset\", arrow::float32(), ReadTableSpecVersion::v3())\n, calibration_scale(this, \"calibration_scale\", arrow::float32(), ReadTableSpecVersion::v3())\n, end_reason(\n      this,\n      \"end_reason\",\n      arrow::dictionary(arrow::int16(), arrow::utf8()),\n      ReadTableSpecVersion::v3())\n, end_reason_forced(this, \"end_reason_forced\", arrow::boolean(), ReadTableSpecVersion::v3())\n, run_info(\n      this,\n      \"run_info\",\n      arrow::dictionary(arrow::int16(), arrow::utf8()),\n      ReadTableSpecVersion::v3())\n, open_pore_level(this, \"open_pore_level\", arrow::float32(), ReadTableSpecVersion::v4())\n{\n}\n\nTableSpecVersion ReadTableSchemaDescription::table_version_from_file_version(\n    Version file_version) const\n{\n    return ReadTableSpecVersion::latest();\n}\n\nResult<std::shared_ptr<ReadTableSchemaDescription const>> read_read_table_schema(\n    SchemaMetadataDescription const & schema_metadata,\n    std::shared_ptr<arrow::Schema> const & schema)\n{\n    auto result = std::make_shared<ReadTableSchemaDescription>();\n    ARROW_RETURN_NOT_OK(ReadTableSchemaDescription::read_schema(result, schema_metadata, schema));\n\n    return result;\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/read_table_schema.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/schema_utils.h\"\n#include \"pod5_format/tuple_utils.h\"\n#include \"pod5_format/types.h\"\n\n#include <memory>\n#include <tuple>\n#include <vector>\n\nnamespace arrow {\nclass KeyValueMetadata;\nclass Schema;\nclass DataType;\nclass StructType;\n}  // namespace arrow\n\nnamespace pod5 {\n\nstruct SchemaMetadataDescription;\n\nclass ReadTableSpecVersion {\npublic:\n    static TableSpecVersion v0() { return TableSpecVersion::first_version(); }\n\n    static TableSpecVersion v1()\n    {\n        // Addition of num_minknow_events and scaling parameters\n        return TableSpecVersion::at_version(1);\n    }\n\n    static TableSpecVersion v2()\n    {\n        // Addition of num_samples parameters\n        return TableSpecVersion::at_version(2);\n    }\n\n    static TableSpecVersion v3()\n    {\n        // Flattening of dictionaries into separate table.\n        return TableSpecVersion::at_version(3);\n    }\n\n    static TableSpecVersion v4()\n    {\n        // Flattening of dictionaries into separate table.\n        return TableSpecVersion::at_version(4);\n    }\n\n    static TableSpecVersion latest() { return v4(); }\n};\n\nclass ReadTableSchemaDescription : public SchemaDescriptionBase {\npublic:\n    ReadTableSchemaDescription();\n\n    ReadTableSchemaDescription(ReadTableSchemaDescription const &) = delete;\n    ReadTableSchemaDescription & operator=(ReadTableSchemaDescription const &) = delete;\n\n    TableSpecVersion table_version_from_file_version(Version file_version) const override;\n\n    // V0 fields\n    Field<0, UuidArray> read_id;\n    ListField<1, arrow::ListArray, arrow::UInt64Array> signal;\n    Field<2, arrow::UInt32Array> read_number;\n    Field<3, arrow::UInt64Array> start;\n    Field<4, arrow::FloatArray> median_before;\n\n    // V1 fields\n    Field<5, arrow::UInt64Array> num_minknow_events;\n    [[deprecated]] Field<6, arrow::FloatArray> tracked_scaling_scale;\n    [[deprecated]] Field<7, arrow::FloatArray> tracked_scaling_shift;\n    [[deprecated]] Field<8, arrow::FloatArray> predicted_scaling_scale;\n    [[deprecated]] Field<9, arrow::FloatArray> predicted_scaling_shift;\n    [[deprecated]] Field<10, arrow::UInt32Array> num_reads_since_mux_change;\n    [[deprecated]] Field<11, arrow::FloatArray> time_since_mux_change;\n\n    // V2 fields\n    Field<12, arrow::UInt64Array> num_samples;\n\n    // V3 fields\n    Field<13, arrow::UInt16Array> channel;\n    Field<14, arrow::UInt8Array> well;\n    Field<15, arrow::DictionaryArray> pore_type;\n    Field<16, arrow::FloatArray> calibration_offset;\n    Field<17, arrow::FloatArray> calibration_scale;\n    Field<18, arrow::DictionaryArray> end_reason;\n    Field<19, arrow::BooleanArray> end_reason_forced;\n    Field<20, arrow::DictionaryArray> run_info;\n\n    // V4 fields\n    Field<21, arrow::FloatArray> open_pore_level;\n\n    // Field Builders only for fields we write in newly generated files.\n    // Should not include fields which are removed in the latest version:\n    using FieldBuilders = FieldBuilder<\n        // V0 fields\n        decltype(read_id),\n        decltype(signal),\n        decltype(read_number),\n        decltype(start),\n        decltype(median_before),\n\n        // V1 fields\n        decltype(num_minknow_events),\n        decltype(tracked_scaling_scale),\n        decltype(tracked_scaling_shift),\n        decltype(predicted_scaling_scale),\n        decltype(predicted_scaling_shift),\n        decltype(num_reads_since_mux_change),\n        decltype(time_since_mux_change),\n\n        // V2 fields\n        decltype(num_samples),\n\n        // V3 fields\n        decltype(channel),\n        decltype(well),\n        decltype(pore_type),\n        decltype(calibration_offset),\n        decltype(calibration_scale),\n        decltype(end_reason),\n        decltype(end_reason_forced),\n        decltype(run_info),\n\n        // V4 fields\n        decltype(open_pore_level)>;\n};\n\nPOD5_FORMAT_EXPORT Result<std::shared_ptr<ReadTableSchemaDescription const>> read_read_table_schema(\n    SchemaMetadataDescription const & schema_metadata,\n    std::shared_ptr<arrow::Schema> const &);\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/read_table_utils.cpp",
    "content": "#include \"pod5_format/read_table_utils.h\"\n\n#include <algorithm>\n\nnamespace pod5 {\n\nReadIdSearchInput::ReadIdSearchInput(gsl::span<Uuid const> const & input_ids)\n: m_search_read_ids(input_ids.size())\n{\n    // Copy in search input:\n    for (std::size_t i = 0; i < input_ids.size(); ++i) {\n        m_search_read_ids[i].id = input_ids[i];\n        m_search_read_ids[i].index = i;\n    }\n\n    // Sort input based on read id:\n    std::sort(\n        m_search_read_ids.begin(), m_search_read_ids.end(), [](auto const & a, auto const & b) {\n            return a.id < b.id;\n        });\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/read_table_utils.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/uuid.h\"\n\n#include <gsl/gsl-lite.hpp>\n\n#include <chrono>\n#include <cstdint>\n#include <string>\n#include <vector>\n\nnamespace pod5 {\n\nusing PoreDictionaryIndex = std::int16_t;\nusing EndReasonDictionaryIndex = std::int16_t;\nusing RunInfoDictionaryIndex = std::int16_t;\n\nclass ReadData {\npublic:\n    ReadData() = default;\n\n    /// \\brief Create a new read data structure to add to a read.\n    /// \\param read_id The read id for the read entry.\n    /// \\param read_number Read number for this read.\n    /// \\param start_sample The sample which this read starts at.\n    /// \\param median_before The median of the read chunk prior to the start of this read.\n    /// \\param end_reason The dictionary index of the end reason name which caused this read to complete.\n    /// \\param end_reason_forced Boolean value indicating if the read end was forced.\n    /// \\param run_info The dictionary index of the run info for this read.\n    /// \\param num_minknow_events The number of minknow events in the read.\n    ReadData(\n        Uuid const & read_id,\n        std::uint32_t read_number,\n        std::uint64_t start_sample,\n        std::uint16_t channel,\n        std::uint8_t well,\n        PoreDictionaryIndex pore_type,\n        float calibration_offset,\n        float calibration_scale,\n        float median_before,\n        EndReasonDictionaryIndex end_reason,\n        bool end_reason_forced,\n        RunInfoDictionaryIndex run_info,\n        std::uint64_t num_minknow_events,\n        float tracked_scaling_scale,\n        float tracked_scaling_shift,\n        float predicted_scaling_scale,\n        float predicted_scaling_shift,\n        std::uint32_t num_reads_since_mux_change,\n        float time_since_mux_change,\n        float open_pore_level)\n    : read_id(read_id)\n    , read_number(read_number)\n    , start_sample(start_sample)\n    , median_before(median_before)\n    , end_reason(end_reason)\n    , end_reason_forced(end_reason_forced)\n    , run_info(run_info)\n    , num_minknow_events(num_minknow_events)\n    , tracked_scaling_scale(tracked_scaling_scale)\n    , tracked_scaling_shift(tracked_scaling_shift)\n    , predicted_scaling_scale(predicted_scaling_scale)\n    , predicted_scaling_shift(predicted_scaling_shift)\n    , num_reads_since_mux_change(num_reads_since_mux_change)\n    , time_since_mux_change(time_since_mux_change)\n    , channel(channel)\n    , well(well)\n    , pore_type(pore_type)\n    , calibration_offset(calibration_offset)\n    , calibration_scale(calibration_scale)\n    , open_pore_level(open_pore_level)\n    {\n    }\n\n    // V1 Fields\n    Uuid read_id;\n    std::uint32_t read_number;\n    std::uint64_t start_sample;\n    float median_before;\n    EndReasonDictionaryIndex end_reason;\n    bool end_reason_forced;\n    RunInfoDictionaryIndex run_info;\n\n    // V2 Fields\n    std::uint64_t num_minknow_events;\n    [[deprecated]] float tracked_scaling_scale;\n    [[deprecated]] float tracked_scaling_shift;\n    [[deprecated]] float predicted_scaling_scale;\n    [[deprecated]] float predicted_scaling_shift;\n    [[deprecated]] std::uint32_t num_reads_since_mux_change;\n    [[deprecated]] float time_since_mux_change;\n\n    // V3 Fields\n    std::uint16_t channel;\n    std::uint8_t well;\n    PoreDictionaryIndex pore_type;\n    float calibration_offset;\n    float calibration_scale;\n\n    // V4 Fields\n    float open_pore_level;\n};\n\ninline bool operator==(ReadData const & a, ReadData const & b)\n{\n    return a.read_id == b.read_id && a.read_number == b.read_number\n           && a.start_sample == b.start_sample && a.median_before == b.median_before\n           && a.end_reason == b.end_reason && a.end_reason_forced == b.end_reason_forced\n           && a.run_info == b.run_info && a.num_minknow_events == b.num_minknow_events\n           && a.tracked_scaling_scale == b.tracked_scaling_scale\n           && a.tracked_scaling_shift == b.tracked_scaling_shift\n           && a.predicted_scaling_scale == b.predicted_scaling_scale\n           && a.predicted_scaling_shift == b.predicted_scaling_shift\n           && a.num_reads_since_mux_change == b.num_reads_since_mux_change\n           && a.time_since_mux_change == b.time_since_mux_change && a.channel == b.channel\n           && a.well == b.well && a.pore_type == b.pore_type\n           && a.calibration_offset == b.calibration_offset\n           && a.calibration_scale == b.calibration_scale && a.open_pore_level == b.open_pore_level;\n}\n\nclass RunInfoData {\npublic:\n    using MapType = std::vector<std::pair<std::string, std::string>>;\n\n    RunInfoData(\n        std::string acquisition_id,\n        std::int64_t acquisition_start_time,\n        std::int16_t adc_max,\n        std::int16_t adc_min,\n        MapType context_tags,\n        std::string experiment_name,\n        std::string flow_cell_id,\n        std::string flow_cell_product_code,\n        std::string protocol_name,\n        std::string protocol_run_id,\n        std::int64_t protocol_start_time,\n        std::string sample_id,\n        std::uint16_t sample_rate,\n        std::string sequencing_kit,\n        std::string sequencer_position,\n        std::string sequencer_position_type,\n        std::string software,\n        std::string system_name,\n        std::string system_type,\n        MapType tracking_id)\n    : acquisition_id(std::move(acquisition_id))\n    , acquisition_start_time(std::move(acquisition_start_time))\n    , adc_max(std::move(adc_max))\n    , adc_min(std::move(adc_min))\n    , context_tags(std::move(context_tags))\n    , experiment_name(std::move(experiment_name))\n    , flow_cell_id(std::move(flow_cell_id))\n    , flow_cell_product_code(std::move(flow_cell_product_code))\n    , protocol_name(std::move(protocol_name))\n    , protocol_run_id(std::move(protocol_run_id))\n    , protocol_start_time(std::move(protocol_start_time))\n    , sample_id(std::move(sample_id))\n    , sample_rate(std::move(sample_rate))\n    , sequencing_kit(std::move(sequencing_kit))\n    , sequencer_position(std::move(sequencer_position))\n    , sequencer_position_type(std::move(sequencer_position_type))\n    , software(std::move(software))\n    , system_name(std::move(system_name))\n    , system_type(std::move(system_type))\n    , tracking_id(std::move(tracking_id))\n    {\n    }\n\n    static std::int64_t convert_from_system_clock(std::chrono::system_clock::time_point value)\n    {\n        return value.time_since_epoch() / std::chrono::milliseconds(1);\n    }\n\n    static std::chrono::system_clock::time_point convert_to_system_clock(\n        std::int64_t since_epoch_ms)\n    {\n        return std::chrono::system_clock::time_point() + std::chrono::milliseconds(since_epoch_ms);\n    }\n\n    std::string acquisition_id;\n    std::int64_t acquisition_start_time;\n    std::int16_t adc_max;\n    std::int16_t adc_min;\n    MapType context_tags;\n    std::string experiment_name;\n    std::string flow_cell_id;\n    std::string flow_cell_product_code;\n    std::string protocol_name;\n    std::string protocol_run_id;\n    std::int64_t protocol_start_time;\n    std::string sample_id;\n    std::uint16_t sample_rate;\n    std::string sequencing_kit;\n    std::string sequencer_position;\n    std::string sequencer_position_type;\n    std::string software;\n    std::string system_name;\n    std::string system_type;\n    MapType tracking_id;\n};\n\ninline bool operator==(RunInfoData const & a, RunInfoData const & b)\n{\n    return a.acquisition_id == b.acquisition_id\n           && a.acquisition_start_time == b.acquisition_start_time && a.adc_max == b.adc_max\n           && a.adc_min == b.adc_min && a.context_tags == b.context_tags\n           && a.experiment_name == b.experiment_name && a.flow_cell_id == b.flow_cell_id\n           && a.flow_cell_product_code == b.flow_cell_product_code\n           && a.protocol_name == b.protocol_name && a.protocol_run_id == b.protocol_run_id\n           && a.protocol_start_time == b.protocol_start_time && a.sample_id == b.sample_id\n           && a.sample_rate == b.sample_rate && a.sequencing_kit == b.sequencing_kit\n           && a.sequencer_position == b.sequencer_position\n           && a.sequencer_position_type == b.sequencer_position_type && a.software == b.software\n           && a.system_name == b.system_name && a.system_type == b.system_type\n           && a.tracking_id == b.tracking_id;\n}\n\nenum class ReadEndReason : std::uint8_t {\n    unknown,\n    mux_change,\n    unblock_mux_change,\n    data_service_unblock_mux_change,\n    signal_positive,\n    signal_negative,\n    api_request,\n    device_data_error,\n    analysis_config_change,\n    paused,\n\n    last_end_reason = paused\n};\n\ninline char const * end_reason_as_string(ReadEndReason reason)\n{\n    static_assert(\n        ReadEndReason::last_end_reason == ReadEndReason::paused,\n        \"Need to add new end reason to this function\");\n    switch (reason) {\n    case ReadEndReason::mux_change:\n        return \"mux_change\";\n    case ReadEndReason::unblock_mux_change:\n        return \"unblock_mux_change\";\n    case ReadEndReason::data_service_unblock_mux_change:\n        return \"data_service_unblock_mux_change\";\n    case ReadEndReason::signal_positive:\n        return \"signal_positive\";\n    case ReadEndReason::signal_negative:\n        return \"signal_negative\";\n    case ReadEndReason::api_request:\n        return \"api_request\";\n    case ReadEndReason::device_data_error:\n        return \"device_data_error\";\n    case ReadEndReason::analysis_config_change:\n        return \"analysis_config_change\";\n    case ReadEndReason::paused:\n        return \"paused\";\n    case ReadEndReason::unknown:\n        break;\n    }\n    return \"unknown\";\n}\n\ninline ReadEndReason end_reason_from_string(std::string const & reason)\n{\n    static_assert(\n        ReadEndReason::last_end_reason == ReadEndReason::paused,\n        \"Need to add new end reason to this function\");\n    if (reason == \"unknown\") {\n        return ReadEndReason::unknown;\n    } else if (reason == \"mux_change\") {\n        return ReadEndReason::mux_change;\n    } else if (reason == \"unblock_mux_change\") {\n        return ReadEndReason::unblock_mux_change;\n    } else if (reason == \"data_service_unblock_mux_change\") {\n        return ReadEndReason::data_service_unblock_mux_change;\n    } else if (reason == \"signal_positive\") {\n        return ReadEndReason::signal_positive;\n    } else if (reason == \"signal_negative\") {\n        return ReadEndReason::signal_negative;\n    } else if (reason == \"api_request\") {\n        return ReadEndReason::api_request;\n    } else if (reason == \"device_data_error\") {\n        return ReadEndReason::device_data_error;\n    } else if (reason == \"analysis_config_change\") {\n        return ReadEndReason::analysis_config_change;\n    } else if (reason == \"paused\") {\n        return ReadEndReason::paused;\n    }\n\n    return ReadEndReason::unknown;\n}\n\n/// \\brief Input query to a search for a number of read ids in a file:\nclass POD5_FORMAT_EXPORT ReadIdSearchInput {\npublic:\n    struct InputId {\n        Uuid id;\n        std::size_t index;\n    };\n\n    ReadIdSearchInput(gsl::span<Uuid const> const & input_ids);\n\n    std::size_t read_id_count() const { return m_search_read_ids.size(); }\n\n    InputId const & operator[](std::size_t i) const { return m_search_read_ids[i]; }\n\nprivate:\n    std::vector<InputId> m_search_read_ids;\n};\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/read_table_writer.cpp",
    "content": "#include \"pod5_format/read_table_writer.h\"\n\n#include \"pod5_format/file_output_stream.h\"\n#include \"pod5_format/internal/tracing/tracing.h\"\n\n#include <arrow/extension_type.h>\n#include <arrow/ipc/writer.h>\n#include <arrow/record_batch.h>\n#include <arrow/type.h>\n#include <arrow/util/compression.h>\n\nnamespace pod5 {\n\nReadTableWriter::ReadTableWriter(\n    std::shared_ptr<arrow::ipc::RecordBatchWriter> && writer,\n    std::shared_ptr<arrow::Schema> && schema,\n    std::shared_ptr<ReadTableSchemaDescription> const & field_locations,\n    std::size_t table_batch_size,\n    std::shared_ptr<PoreWriter> const & pore_writer,\n    std::shared_ptr<EndReasonWriter> const & end_reason_writer,\n    std::shared_ptr<RunInfoWriter> const & run_info_writer,\n    std::shared_ptr<FileOutputStream> const & output_stream,\n    arrow::MemoryPool * pool)\n: m_schema(schema)\n, m_field_locations(field_locations)\n, m_table_batch_size(table_batch_size)\n, m_writer(std::move(writer))\n, m_field_builders(m_field_locations, pool)\n, m_output_stream{output_stream}\n{\n    m_field_builders.get_builder(m_field_locations->pore_type).set_dict_writer(pore_writer);\n    m_field_builders.get_builder(m_field_locations->end_reason).set_dict_writer(end_reason_writer);\n    m_field_builders.get_builder(m_field_locations->run_info).set_dict_writer(run_info_writer);\n}\n\nReadTableWriter::ReadTableWriter(ReadTableWriter && other) = default;\nReadTableWriter & ReadTableWriter::operator=(ReadTableWriter &&) = default;\n\nReadTableWriter::~ReadTableWriter()\n{\n    if (m_writer) {\n        (void)close();\n    }\n}\n\nResult<std::size_t> ReadTableWriter::add_read(\n    ReadData const & read_data,\n    gsl::span<SignalTableRowIndex const> const & signal,\n    std::uint64_t signal_duration)\n{\n    POD5_TRACE_FUNCTION();\n    if (!m_writer) {\n        return Status::IOError(\"Writer terminated\");\n    }\n\n    ARROW_RETURN_NOT_OK(reserve_rows());\n\n    auto row_id = m_written_batched_row_count + m_current_batch_row_count;\n    ARROW_RETURN_NOT_OK(m_field_builders.append(\n        // V0 Fields\n        read_data.read_id,\n        signal,\n        read_data.read_number,\n        read_data.start_sample,\n        read_data.median_before,\n\n        // V1 Fields\n        read_data.num_minknow_events,\n        read_data.tracked_scaling_scale,\n        read_data.tracked_scaling_shift,\n        read_data.predicted_scaling_scale,\n        read_data.predicted_scaling_shift,\n        read_data.num_reads_since_mux_change,\n        read_data.time_since_mux_change,\n\n        // V2 Fields\n        signal_duration,\n\n        // V3 Fields\n        read_data.channel,\n        read_data.well,\n        read_data.pore_type,\n        read_data.calibration_offset,\n        read_data.calibration_scale,\n        read_data.end_reason,\n        read_data.end_reason_forced,\n        read_data.run_info,\n\n        // V4 Fields\n        read_data.open_pore_level));\n\n    ++m_current_batch_row_count;\n\n    if (m_current_batch_row_count >= m_table_batch_size) {\n        ARROW_RETURN_NOT_OK(write_batch());\n    }\n    return row_id;\n}\n\nStatus ReadTableWriter::close()\n{\n    // Check for already closed\n    if (!m_writer) {\n        return Status::OK();\n    }\n\n    ARROW_RETURN_NOT_OK(write_batch());\n    ARROW_RETURN_NOT_OK(m_writer->Close());\n    m_writer = nullptr;\n    return Status::OK();\n}\n\nStatus ReadTableWriter::write_batch(arrow::RecordBatch const & record_batch)\n{\n    ARROW_RETURN_NOT_OK(m_writer->WriteRecordBatch(record_batch));\n    return m_output_stream->batch_complete();\n}\n\nStatus ReadTableWriter::write_batch()\n{\n    POD5_TRACE_FUNCTION();\n    if (m_current_batch_row_count == 0) {\n        return Status::OK();\n    }\n\n    if (!m_writer) {\n        return Status::IOError(\"Writer terminated\");\n    }\n\n    ARROW_ASSIGN_OR_RAISE(auto columns, m_field_builders.finish_columns());\n\n    auto const record_batch =\n        arrow::RecordBatch::Make(m_schema, m_current_batch_row_count, std::move(columns));\n\n    m_written_batched_row_count += m_current_batch_row_count;\n    m_current_batch_row_count = 0;\n\n    ARROW_RETURN_NOT_OK(m_writer->WriteRecordBatch(*record_batch));\n    return m_output_stream->batch_complete();\n}\n\nStatus ReadTableWriter::reserve_rows()\n{\n    // Only reserve if we have not already reserved (at the start of a batch)\n    if (m_current_batch_row_count > 0) {\n        return arrow::Status::OK();\n    }\n\n    return m_field_builders.reserve(m_table_batch_size);\n}\n\nResult<ReadTableWriter> make_read_table_writer(\n    std::shared_ptr<FileOutputStream> const & sink,\n    std::shared_ptr<arrow::KeyValueMetadata const> const & metadata,\n    std::size_t table_batch_size,\n    std::shared_ptr<PoreWriter> const & pore_writer,\n    std::shared_ptr<EndReasonWriter> const & end_reason_writer,\n    std::shared_ptr<RunInfoWriter> const & run_info_writer,\n    arrow::MemoryPool * pool)\n{\n    auto field_locations = std::make_shared<ReadTableSchemaDescription>();\n    auto schema = field_locations->make_writer_schema(metadata);\n\n    arrow::ipc::IpcWriteOptions options;\n    options.memory_pool = pool;\n    options.emit_dictionary_deltas = true;\n    // todo... consider:\n    //ARROW_ASSIGN_OR_RAISE(options.codec, arrow::util::Codec::Create(arrow::Compression::LZ4_FRAME));\n\n    ARROW_ASSIGN_OR_RAISE(auto writer, arrow::ipc::MakeFileWriter(sink, schema, options, metadata));\n\n    auto read_table_writer = ReadTableWriter(\n        std::move(writer),\n        std::move(schema),\n        field_locations,\n        table_batch_size,\n        pore_writer,\n        end_reason_writer,\n        run_info_writer,\n        sink,\n        pool);\n\n    return read_table_writer;\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/read_table_writer.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/read_table_schema.h\"\n#include \"pod5_format/read_table_writer_utils.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/schema_field_builder.h\"\n#include \"pod5_format/signal_table_utils.h\"\n\n#include <arrow/array/builder_dict.h>\n#include <arrow/io/type_fwd.h>\n\nnamespace arrow {\nclass Schema;\n\nnamespace ipc {\nclass RecordBatchWriter;\n}\n}  // namespace arrow\n\nnamespace pod5 {\n\nclass FileOutputStream;\n\nclass POD5_FORMAT_EXPORT ReadTableWriter {\npublic:\n    ReadTableWriter(\n        std::shared_ptr<arrow::ipc::RecordBatchWriter> && writer,\n        std::shared_ptr<arrow::Schema> && schema,\n        std::shared_ptr<ReadTableSchemaDescription> const & field_locations,\n        std::size_t table_batch_size,\n        std::shared_ptr<PoreWriter> const & pore_writer,\n        std::shared_ptr<EndReasonWriter> const & end_reason_writer,\n        std::shared_ptr<RunInfoWriter> const & run_info_writer,\n        std::shared_ptr<FileOutputStream> const & output_stream,\n        arrow::MemoryPool * pool);\n    ReadTableWriter(ReadTableWriter &&);\n    ReadTableWriter & operator=(ReadTableWriter &&);\n    ReadTableWriter(ReadTableWriter const &) = delete;\n    ReadTableWriter & operator=(ReadTableWriter const &) = delete;\n    ~ReadTableWriter();\n\n    /// \\brief Add a read to the read table, adding to the current batch.\n    /// \\param read_data The data to add as a read.\n    /// \\param signal List of signal table row indices that belong to this read.\n    /// \\param signal_duration The length of the read in samples.\n    /// \\returns The row index of the inserted read, or a status on failure.\n    Result<std::size_t> add_read(\n        ReadData const & read_data,\n        gsl::span<SignalTableRowIndex const> const & signal,\n        std::uint64_t signal_duration);\n\n    /// \\brief Close this writer, signaling no further data will be written to the writer.\n    Status close();\n\n    /// \\brief Reserve space for future row writes, called automatically when a flush occurs.\n    Status reserve_rows();\n\n    /// \\brief Find the schema for the table\n    std::shared_ptr<arrow::Schema> const & schema() const { return m_schema; }\n\n    /// \\brief Flush passed data into the writer as a record batch.\n    Status write_batch(arrow::RecordBatch const &);\n\nprivate:\n    /// \\brief Flush buffered data into the writer as a record batch.\n    Status write_batch();\n\n    std::shared_ptr<arrow::Schema> m_schema;\n    std::shared_ptr<ReadTableSchemaDescription> m_field_locations;\n    std::size_t m_table_batch_size;\n\n    std::shared_ptr<arrow::ipc::RecordBatchWriter> m_writer;\n\n    ReadTableSchemaDescription::FieldBuilders m_field_builders;\n\n    std::size_t m_written_batched_row_count = 0;\n    std::size_t m_current_batch_row_count = 0;\n    std::shared_ptr<FileOutputStream> m_output_stream;\n};\n\n/// \\brief Make a new writer for a read table.\n/// \\param sink Sink to be used for output of the table.\n/// \\param metadata Metadata to be applied to the table schema.\n/// \\param table_batch_size The size of each batch written for the table.\n/// \\param pool Pool to be used for building table in memory.\n/// \\returns The writer for the new table.\nPOD5_FORMAT_EXPORT Result<ReadTableWriter> make_read_table_writer(\n    std::shared_ptr<FileOutputStream> const & sink,\n    std::shared_ptr<arrow::KeyValueMetadata const> const & metadata,\n    std::size_t table_batch_size,\n    std::shared_ptr<PoreWriter> const & pore_writer,\n    std::shared_ptr<EndReasonWriter> const & end_reason_writer,\n    std::shared_ptr<RunInfoWriter> const & run_info_writer,\n    arrow::MemoryPool * pool);\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/read_table_writer_utils.cpp",
    "content": "#include \"pod5_format/read_table_writer_utils.h\"\n\n#include \"pod5_format/read_table_schema.h\"\n\n#include <arrow/array/array_dict.h>\n#include <arrow/array/builder_binary.h>\n#include <arrow/array/builder_nested.h>\n#include <arrow/array/builder_primitive.h>\n\nnamespace pod5 {\n\nnamespace detail {\n\narrow::Result<std::shared_ptr<arrow::ArrayData>> get_array_data(\n    std::shared_ptr<arrow::DataType> const & type,\n    StringDictionaryKeyBuilder const & builder,\n    std::size_t expected_length)\n{\n    auto const value_data = builder.get_string_data();\n    if (!value_data) {\n        return Status::Invalid(\"Missing array value data for dictionary\");\n    }\n\n    arrow::TypedBufferBuilder<std::int32_t> offset_builder;\n    auto const & offset_data = builder.get_typed_offset_data();\n    if (offset_data.size() != expected_length) {\n        return Status::Invalid(\"Invalid size for field in struct\");\n    }\n    ARROW_RETURN_NOT_OK(offset_builder.Append(offset_data.data(), offset_data.size()));\n    // Append final offset - size of value data.\n    ARROW_RETURN_NOT_OK(offset_builder.Append(value_data->size()));\n\n    std::shared_ptr<arrow::Buffer> offsets;\n    ARROW_RETURN_NOT_OK(offset_builder.Finish(&offsets));\n\n    return arrow::ArrayData::Make(type, expected_length, {nullptr, offsets, value_data}, 0, 0);\n}\n\n}  // namespace detail\n\narrow::Result<std::shared_ptr<PoreWriter>> make_pore_writer(arrow::MemoryPool * pool)\n{\n    return std::make_shared<PoreWriter>(pool);\n}\n\narrow::Result<std::shared_ptr<EndReasonWriter>> make_end_reason_writer(arrow::MemoryPool * pool)\n{\n    std::shared_ptr<arrow::StringArray> end_reasons;\n    {\n        arrow::StringBuilder builder(pool);\n        for (int end_reason = 0; end_reason <= (int)ReadEndReason::last_end_reason; ++end_reason) {\n            ARROW_RETURN_NOT_OK(builder.Append(end_reason_as_string((ReadEndReason)end_reason)));\n        }\n\n        ARROW_RETURN_NOT_OK(builder.Finish(&end_reasons));\n    }\n\n    return std::make_shared<EndReasonWriter>(end_reasons);\n}\n\narrow::Result<std::shared_ptr<RunInfoWriter>> make_run_info_writer(arrow::MemoryPool * pool)\n{\n    return std::make_shared<RunInfoWriter>(pool);\n}\n\npod5::Result<std::shared_ptr<arrow::Array>> DictionaryWriter::build_dictionary_array(\n    std::shared_ptr<arrow::Array> const & indices)\n{\n    ARROW_ASSIGN_OR_RAISE(auto res, get_value_array());\n    return arrow::DictionaryArray::FromArrays(indices, res);\n}\n\nPoreWriter::PoreWriter(arrow::MemoryPool * pool) : m_builder(pool) {}\n\npod5::Result<std::shared_ptr<arrow::Array>> PoreWriter::get_value_array()\n{\n    ARROW_ASSIGN_OR_RAISE(auto array_data, get_array_data(arrow::utf8(), m_builder, item_count()));\n    return std::make_shared<arrow::StringArray>(array_data);\n}\n\nstd::size_t PoreWriter::item_count() { return m_builder.length(); }\n\nEndReasonWriter::EndReasonWriter(std::shared_ptr<arrow::StringArray> const & end_reasons)\n: m_end_reasons(end_reasons)\n{\n}\n\npod5::Result<std::shared_ptr<arrow::Array>> EndReasonWriter::get_value_array()\n{\n    return m_end_reasons;\n}\n\nstd::size_t EndReasonWriter::item_count() { return m_end_reasons->length(); }\n\nRunInfoWriter::RunInfoWriter(arrow::MemoryPool * pool) : m_builder(pool) {}\n\npod5::Result<std::shared_ptr<arrow::Array>> RunInfoWriter::get_value_array()\n{\n    ARROW_ASSIGN_OR_RAISE(auto array_data, get_array_data(arrow::utf8(), m_builder, item_count()));\n    return std::make_shared<arrow::StringArray>(array_data);\n}\n\nstd::size_t RunInfoWriter::item_count() { return m_builder.length(); }\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/read_table_writer_utils.h",
    "content": "#pragma once\n\n#include \"pod5_format/dictionary_writer.h\"\n#include \"pod5_format/expandable_buffer.h\"\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/read_table_utils.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/tuple_utils.h\"\n\n#include <arrow/array/array_binary.h>\n#include <arrow/io/type_fwd.h>\n#include <arrow/util/bit_util.h>\n#include <gsl/gsl-lite.hpp>\n\n#include <chrono>\n#include <cstdint>\n#include <map>\n\nnamespace pod5 {\n\nnamespace detail {\n\nclass StringDictionaryKeyBuilder {\npublic:\n    StringDictionaryKeyBuilder(arrow::MemoryPool * pool = nullptr)\n    : m_offset_values(pool)\n    , m_string_values(pool)\n    {\n    }\n\n    arrow::Status init_buffer(arrow::MemoryPool * pool)\n    {\n        ARROW_RETURN_NOT_OK(m_offset_values.init_buffer(pool));\n        return m_string_values.init_buffer(pool);\n    }\n\n    arrow::Status append(std::string const & value)\n    {\n        ARROW_RETURN_NOT_OK(m_offset_values.append(m_string_values.size()));\n        return m_string_values.append_array(\n            gsl::make_span(value.data(), value.size()).as_span<std::uint8_t const>());\n    }\n\n    std::size_t length() const { return m_offset_values.size(); }\n\n    std::shared_ptr<arrow::Buffer> get_string_data() const { return m_string_values.get_buffer(); }\n\n    gsl::span<std::int32_t const> get_typed_offset_data() const\n    {\n        return m_offset_values.get_data_span();\n    }\n\nprivate:\n    ExpandableBuffer<std::int32_t> m_offset_values;\n    ExpandableBuffer<std::uint8_t> m_string_values;\n};\n\n}  // namespace detail\n\nclass POD5_FORMAT_EXPORT PoreWriter : public DictionaryWriter {\npublic:\n    PoreWriter(arrow::MemoryPool * pool);\n\n    pod5::Result<PoreDictionaryIndex> add(std::string const & pore_type)\n    {\n        auto const index = item_count();\n\n        if (index >= std::size_t(std::numeric_limits<std::int16_t>::max())) {\n            return arrow::Status::Invalid(\n                \"Failed to add pore to dictionary, too many indices in file\");\n        }\n\n        ARROW_RETURN_NOT_OK(m_builder.append(pore_type));\n        return index;\n    }\n\n    pod5::Result<std::shared_ptr<arrow::Array>> get_value_array() override;\n    std::size_t item_count() override;\n\nprivate:\n    detail::StringDictionaryKeyBuilder m_builder;\n};\n\nclass POD5_FORMAT_EXPORT EndReasonWriter : public DictionaryWriter {\npublic:\n    EndReasonWriter(std::shared_ptr<arrow::StringArray> const & end_reasons);\n\n    pod5::Result<EndReasonDictionaryIndex> lookup(ReadEndReason end_reason) const\n    {\n        if (end_reason > ReadEndReason::last_end_reason) {\n            return pod5::Status::Invalid(\"Invalid read end reason requested\");\n        }\n        return EndReasonDictionaryIndex(end_reason);\n    }\n\n    pod5::Result<std::shared_ptr<arrow::Array>> get_value_array() override;\n    std::size_t item_count() override;\n\nprivate:\n    std::shared_ptr<arrow::StringArray> m_end_reasons;\n};\n\nclass POD5_FORMAT_EXPORT RunInfoWriter : public DictionaryWriter {\npublic:\n    RunInfoWriter(arrow::MemoryPool * pool);\n\n    pod5::Result<RunInfoDictionaryIndex> add(std::string const & acquisition_id)\n    {\n        auto const index = item_count();\n\n        if (index >= std::size_t(std::numeric_limits<std::int16_t>::max())) {\n            return arrow::Status::Invalid(\n                \"Failed to add run info to dictionary, too many indices in file\");\n        }\n\n        ARROW_RETURN_NOT_OK(m_builder.append(acquisition_id));\n        return index;\n    }\n\n    pod5::Result<std::shared_ptr<arrow::Array>> get_value_array() override;\n    std::size_t item_count() override;\n\nprivate:\n    detail::StringDictionaryKeyBuilder m_builder;\n};\n\nPOD5_FORMAT_EXPORT arrow::Result<std::shared_ptr<PoreWriter>> make_pore_writer(\n    arrow::MemoryPool * pool);\n\nPOD5_FORMAT_EXPORT arrow::Result<std::shared_ptr<EndReasonWriter>> make_end_reason_writer(\n    arrow::MemoryPool * pool);\n\nPOD5_FORMAT_EXPORT arrow::Result<std::shared_ptr<RunInfoWriter>> make_run_info_writer(\n    arrow::MemoryPool * pool);\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/result.h",
    "content": "#pragma once\n\n#include <arrow/result.h>\n\nnamespace pod5 {\n\n/// pod5::Result is just an Arrow Result right now.\ntemplate <typename R>\nusing Result = arrow::Result<R>;\nusing Status = arrow::Status;\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/run_info_table_reader.cpp",
    "content": "#include \"pod5_format/run_info_table_reader.h\"\n\n#include \"pod5_format/schema_metadata.h\"\n#include \"pod5_format/schema_utils.h\"\n\n#include <arrow/array/array_binary.h>\n#include <arrow/array/array_dict.h>\n#include <arrow/array/array_nested.h>\n#include <arrow/array/array_primitive.h>\n#include <arrow/ipc/reader.h>\n\n#include <algorithm>\n\nnamespace pod5 {\n\ninline std::vector<std::pair<std::string, std::string>> value_for_map(\n    std::shared_ptr<arrow::MapArray> const & map_array,\n    std::size_t row_index)\n{\n    std::size_t offset = map_array->value_offset(row_index);\n    std::size_t length = map_array->value_length(row_index);\n\n    auto const & keys = std::dynamic_pointer_cast<arrow::StringArray>(map_array->keys());\n    auto const & items = std::dynamic_pointer_cast<arrow::StringArray>(map_array->items());\n\n    std::vector<std::pair<std::string, std::string>> result;\n    for (std::size_t i = offset; i < offset + length; ++i) {\n        result.push_back(std::make_pair(keys->GetString(i), items->GetString(i)));\n    }\n    return result;\n}\n\nRunInfoTableRecordBatch::RunInfoTableRecordBatch(\n    std::shared_ptr<arrow::RecordBatch> && batch,\n    std::shared_ptr<RunInfoTableSchemaDescription const> const & field_locations)\n: TableRecordBatch(std::move(batch))\n, m_field_locations(field_locations)\n{\n}\n\nRunInfoTableRecordBatch::RunInfoTableRecordBatch(RunInfoTableRecordBatch && other)\n: TableRecordBatch(std::move(other))\n{\n    m_field_locations = std::move(other.m_field_locations);\n}\n\nRunInfoTableRecordBatch & RunInfoTableRecordBatch::operator=(RunInfoTableRecordBatch && other)\n{\n    TableRecordBatch & base = *this;\n    base = other;\n\n    m_field_locations = std::move(other.m_field_locations);\n    return *this;\n}\n\nResult<RunInfoTableRecordColumns> RunInfoTableRecordBatch::columns() const\n{\n    RunInfoTableRecordColumns result;\n    result.table_version = m_field_locations->table_version();\n\n    auto const & bat = batch();\n\n    // V0 fields:\n    result.acquisition_id = find_column(bat, m_field_locations->acquisition_id);\n    result.acquisition_start_time = find_column(bat, m_field_locations->acquisition_start_time);\n    result.adc_max = find_column(bat, m_field_locations->adc_max);\n    result.adc_min = find_column(bat, m_field_locations->adc_min);\n    result.context_tags = find_column(bat, m_field_locations->context_tags);\n    result.experiment_name = find_column(bat, m_field_locations->experiment_name);\n    result.flow_cell_id = find_column(bat, m_field_locations->flow_cell_id);\n    result.flow_cell_product_code = find_column(bat, m_field_locations->flow_cell_product_code);\n    result.protocol_name = find_column(bat, m_field_locations->protocol_name);\n    result.protocol_run_id = find_column(bat, m_field_locations->protocol_run_id);\n    result.protocol_start_time = find_column(bat, m_field_locations->protocol_start_time);\n    result.sample_id = find_column(bat, m_field_locations->sample_id);\n    result.sample_rate = find_column(bat, m_field_locations->sample_rate);\n    result.sequencing_kit = find_column(bat, m_field_locations->sequencing_kit);\n    result.sequencer_position = find_column(bat, m_field_locations->sequencer_position);\n    result.sequencer_position_type = find_column(bat, m_field_locations->sequencer_position_type);\n    result.software = find_column(bat, m_field_locations->software);\n    result.system_name = find_column(bat, m_field_locations->system_name);\n    result.system_type = find_column(bat, m_field_locations->system_type);\n    result.tracking_id = find_column(bat, m_field_locations->tracking_id);\n\n    return result;\n}\n\n//---------------------------------------------------------------------------------------------------------------------\n\nRunInfoTableReader::RunInfoTableReader(\n    std::shared_ptr<void> && input_source,\n    std::shared_ptr<arrow::ipc::RecordBatchFileReader> && reader,\n    std::shared_ptr<RunInfoTableSchemaDescription const> const & field_locations,\n    SchemaMetadataDescription && schema_metadata,\n    arrow::MemoryPool * pool)\n: TableReader(std::move(input_source), std::move(reader), std::move(schema_metadata), pool)\n, m_field_locations(field_locations)\n{\n}\n\nRunInfoTableReader::RunInfoTableReader(RunInfoTableReader && other)\n: TableReader(std::move(other))\n, m_field_locations(std::move(other.m_field_locations))\n{\n}\n\nRunInfoTableReader & RunInfoTableReader::operator=(RunInfoTableReader && other)\n{\n    static_cast<TableReader &>(*this) = std::move(static_cast<TableReader &>(*this));\n    m_field_locations = std::move(other.m_field_locations);\n    return *this;\n}\n\nResult<RunInfoTableRecordBatch> RunInfoTableReader::read_record_batch(std::size_t i) const\n{\n    std::lock_guard<std::mutex> l(m_batch_get_mutex);\n    ARROW_ASSIGN_OR_RAISE(auto record_batch, TableReader::ReadRecordBatch(i));\n    return RunInfoTableRecordBatch{std::move(record_batch), m_field_locations};\n}\n\nResult<std::shared_ptr<RunInfoData const>> RunInfoTableReader::find_run_info(\n    std::string const & acquisition_id) const\n{\n    std::lock_guard<std::mutex> l(m_run_info_lookup_mutex);\n    auto it = m_run_info_lookup.find(acquisition_id);\n    if (it != m_run_info_lookup.end()) {\n        return it->second;\n    }\n\n    ARROW_RETURN_NOT_OK(prepare_run_infos_vector());\n\n    std::shared_ptr<RunInfoData const> run_info = nullptr;\n    std::size_t glb_run_info_index = 0;\n    for (std::size_t i = 0; i < num_record_batches(); ++i) {\n        ARROW_ASSIGN_OR_RAISE(auto batch, read_record_batch(i));\n        auto acq_id = find_column(batch.batch(), m_field_locations->acquisition_id);\n\n        for (std::size_t j = 0; j < batch.num_rows(); ++j) {\n            if (acq_id->Value(j) == acquisition_id) {\n                ARROW_ASSIGN_OR_RAISE(\n                    run_info, load_run_info_from_batch(batch, j, glb_run_info_index++));\n                break;\n            }\n        }\n\n        if (run_info) {\n            break;\n        }\n    }\n\n    if (!run_info) {\n        return arrow::Status::Invalid(\n            \"Failed to find acquisition id '\", acquisition_id, \"' in run info table\");\n    }\n\n    return run_info;\n}\n\nResult<std::shared_ptr<RunInfoData const>> RunInfoTableReader::get_run_info(std::size_t index) const\n{\n    ARROW_RETURN_NOT_OK(prepare_run_infos_vector());\n\n    if (index < 0 || index >= m_run_infos.size()) {\n        return arrow::Status::IndexError(\n            \"Invalid index into run infos (expected \", index, \" < \", m_run_infos.size(), \")\");\n    }\n\n    if (m_run_infos[index]) {\n        return m_run_infos[index];\n    }\n\n    ARROW_ASSIGN_OR_RAISE(auto first_batch, read_record_batch(0));\n    auto const batch_size = first_batch.num_rows();\n\n    auto const batch_idx = index / batch_size;\n    auto const batch_row = index - (batch_idx * batch_size);\n\n    if (batch_idx >= num_record_batches()) {\n        return Status::Invalid(\"Row outside batch bounds\");\n    }\n\n    ARROW_ASSIGN_OR_RAISE(auto batch, read_record_batch(batch_idx));\n\n    return load_run_info_from_batch(batch, batch_row, index);\n}\n\nResult<std::size_t> RunInfoTableReader::get_run_info_count() const\n{\n    auto batch_count = num_record_batches();\n    if (batch_count == 0) {\n        return 0;\n    }\n\n    ARROW_ASSIGN_OR_RAISE(auto first_batch, read_record_batch(0));\n    ARROW_ASSIGN_OR_RAISE(auto last_batch, read_record_batch(batch_count - 1));\n\n    return (batch_count - 1) * first_batch.num_rows() + last_batch.num_rows();\n}\n\nResult<std::shared_ptr<RunInfoData const>> RunInfoTableReader::load_run_info_from_batch(\n    RunInfoTableRecordBatch const & batch,\n    std::size_t batch_index,\n    std::size_t global_index) const\n{\n    ARROW_ASSIGN_OR_RAISE(auto columns, batch.columns());\n\n    auto acquisition_id = columns.acquisition_id->GetString(batch_index);\n    auto run_info = std::make_shared<RunInfoData>(\n        acquisition_id,\n        columns.acquisition_start_time->Value(batch_index),\n        columns.adc_max->Value(batch_index),\n        columns.adc_min->Value(batch_index),\n        value_for_map(columns.context_tags, batch_index),\n        columns.experiment_name->GetString(batch_index),\n        columns.flow_cell_id->GetString(batch_index),\n        columns.flow_cell_product_code->GetString(batch_index),\n        columns.protocol_name->GetString(batch_index),\n        columns.protocol_run_id->GetString(batch_index),\n        columns.protocol_start_time->Value(batch_index),\n        columns.sample_id->GetString(batch_index),\n        columns.sample_rate->Value(batch_index),\n        columns.sequencing_kit->GetString(batch_index),\n        columns.sequencer_position->GetString(batch_index),\n        columns.sequencer_position_type->GetString(batch_index),\n        columns.software->GetString(batch_index),\n        columns.system_name->GetString(batch_index),\n        columns.system_type->GetString(batch_index),\n        value_for_map(columns.tracking_id, batch_index));\n\n    // Cache run info for later retrieval by index:\n    m_run_infos[global_index] = run_info;\n    m_run_info_lookup[acquisition_id] = run_info;\n    return run_info;\n}\n\narrow::Status RunInfoTableReader::prepare_run_infos_vector() const\n{\n    if (m_run_infos.empty()) {\n        ARROW_ASSIGN_OR_RAISE(auto row_count, get_run_info_count())\n        m_run_infos.resize(row_count);\n    }\n\n    return Status::OK();\n}\n\n//---------------------------------------------------------------------------------------------------------------------\n\nResult<RunInfoTableReader> make_run_info_table_reader(\n    std::shared_ptr<arrow::io::RandomAccessFile> const & input,\n    arrow::MemoryPool * pool)\n{\n    arrow::ipc::IpcReadOptions options;\n    options.memory_pool = pool;\n\n    ARROW_ASSIGN_OR_RAISE(auto reader, arrow::ipc::RecordBatchFileReader::Open(input, options));\n\n    auto read_metadata_key_values = reader->schema()->metadata();\n    if (!read_metadata_key_values) {\n        return Status::IOError(\"Missing metadata on run info table schema\");\n    }\n    ARROW_ASSIGN_OR_RAISE(\n        auto read_metadata, read_schema_key_value_metadata(read_metadata_key_values));\n    ARROW_ASSIGN_OR_RAISE(\n        auto field_locations, read_run_info_table_schema(read_metadata, reader->schema()));\n\n    return RunInfoTableReader(\n        {input}, std::move(reader), field_locations, std::move(read_metadata), pool);\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/run_info_table_reader.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/read_table_utils.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/run_info_table_schema.h\"\n#include \"pod5_format/schema_metadata.h\"\n#include \"pod5_format/table_reader.h\"\n#include \"pod5_format/types.h\"\n\n#include <arrow/io/type_fwd.h>\n#include <gsl/gsl-lite.hpp>\n\n#include <mutex>\n#include <unordered_map>\n\nnamespace arrow {\nclass Schema;\n\nnamespace io {\nclass RandomAccessFile;\n}\n\nnamespace ipc {\nclass RecordBatchFileReader;\n}\n}  // namespace arrow\n\nnamespace pod5 {\n\nstruct RunInfoTableRecordColumns {\n    // V0 Fields\n    std::shared_ptr<arrow::StringArray> acquisition_id;\n    std::shared_ptr<arrow::TimestampArray> acquisition_start_time;\n    std::shared_ptr<arrow::Int16Array> adc_max;\n    std::shared_ptr<arrow::Int16Array> adc_min;\n    std::shared_ptr<arrow::MapArray> context_tags;\n    std::shared_ptr<arrow::StringArray> experiment_name;\n    std::shared_ptr<arrow::StringArray> flow_cell_id;\n    std::shared_ptr<arrow::StringArray> flow_cell_product_code;\n    std::shared_ptr<arrow::StringArray> protocol_name;\n    std::shared_ptr<arrow::StringArray> protocol_run_id;\n    std::shared_ptr<arrow::TimestampArray> protocol_start_time;\n    std::shared_ptr<arrow::StringArray> sample_id;\n    std::shared_ptr<arrow::UInt16Array> sample_rate;\n    std::shared_ptr<arrow::StringArray> sequencing_kit;\n    std::shared_ptr<arrow::StringArray> sequencer_position;\n    std::shared_ptr<arrow::StringArray> sequencer_position_type;\n    std::shared_ptr<arrow::StringArray> software;\n    std::shared_ptr<arrow::StringArray> system_name;\n    std::shared_ptr<arrow::StringArray> system_type;\n    std::shared_ptr<arrow::MapArray> tracking_id;\n\n    TableSpecVersion table_version;\n};\n\nclass POD5_FORMAT_EXPORT RunInfoTableRecordBatch : public TableRecordBatch {\npublic:\n    RunInfoTableRecordBatch(\n        std::shared_ptr<arrow::RecordBatch> && batch,\n        std::shared_ptr<RunInfoTableSchemaDescription const> const & field_locations);\n    RunInfoTableRecordBatch(RunInfoTableRecordBatch &&);\n    RunInfoTableRecordBatch & operator=(RunInfoTableRecordBatch &&);\n\n    Result<RunInfoTableRecordColumns> columns() const;\n\nprivate:\n    std::shared_ptr<RunInfoTableSchemaDescription const> m_field_locations;\n};\n\nclass POD5_FORMAT_EXPORT RunInfoTableReader : public TableReader {\npublic:\n    RunInfoTableReader(\n        std::shared_ptr<void> && input_source,\n        std::shared_ptr<arrow::ipc::RecordBatchFileReader> && reader,\n        std::shared_ptr<RunInfoTableSchemaDescription const> const & field_locations,\n        SchemaMetadataDescription && schema_metadata,\n        arrow::MemoryPool * pool);\n\n    RunInfoTableReader(RunInfoTableReader && other);\n    RunInfoTableReader & operator=(RunInfoTableReader && other);\n\n    Result<RunInfoTableRecordBatch> read_record_batch(std::size_t i) const;\n\n    Result<std::shared_ptr<RunInfoData const>> find_run_info(\n        std::string const & acquisition_id) const;\n\n    Result<std::shared_ptr<RunInfoData const>> get_run_info(std::size_t index) const;\n    Result<std::size_t> get_run_info_count() const;\n\nprivate:\n    Result<std::shared_ptr<RunInfoData const>> load_run_info_from_batch(\n        RunInfoTableRecordBatch const & batch,\n        std::size_t batch_index,\n        std::size_t global_index) const;\n    arrow::Status prepare_run_infos_vector() const;\n\n    std::shared_ptr<RunInfoTableSchemaDescription const> m_field_locations;\n    mutable std::mutex m_batch_get_mutex;\n    mutable std::unordered_map<std::string, std::shared_ptr<RunInfoData const>> m_run_info_lookup;\n    mutable std::vector<std::shared_ptr<RunInfoData const>> m_run_infos;\n    mutable std::mutex m_run_info_lookup_mutex;\n};\n\nPOD5_FORMAT_EXPORT Result<RunInfoTableReader> make_run_info_table_reader(\n    std::shared_ptr<arrow::io::RandomAccessFile> const & sink,\n    arrow::MemoryPool * pool);\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/run_info_table_schema.cpp",
    "content": "#include \"pod5_format/run_info_table_schema.h\"\n\n#include \"pod5_format/schema_metadata.h\"\n#include \"pod5_format/types.h\"\n\nnamespace pod5 {\n\nRunInfoTableSchemaDescription::RunInfoTableSchemaDescription()\n: SchemaDescriptionBase(RunInfoTableSpecVersion::latest())\n// V0 Fields\n, acquisition_id(this, \"acquisition_id\", arrow::utf8(), RunInfoTableSpecVersion::v0())\n, acquisition_start_time(\n      this,\n      \"acquisition_start_time\",\n      arrow::timestamp(arrow::TimeUnit::MILLI, \"UTC\"),\n      RunInfoTableSpecVersion::v0())\n, adc_max(this, \"adc_max\", arrow::int16(), RunInfoTableSpecVersion::v0())\n, adc_min(this, \"adc_min\", arrow::int16(), RunInfoTableSpecVersion::v0())\n, context_tags(\n      this,\n      \"context_tags\",\n      arrow::map(arrow::utf8(), arrow::utf8()),\n      RunInfoTableSpecVersion::v0())\n, experiment_name(this, \"experiment_name\", arrow::utf8(), RunInfoTableSpecVersion::v0())\n, flow_cell_id(this, \"flow_cell_id\", arrow::utf8(), RunInfoTableSpecVersion::v0())\n, flow_cell_product_code(\n      this,\n      \"flow_cell_product_code\",\n      arrow::utf8(),\n      RunInfoTableSpecVersion::v0())\n, protocol_name(this, \"protocol_name\", arrow::utf8(), RunInfoTableSpecVersion::v0())\n, protocol_run_id(this, \"protocol_run_id\", arrow::utf8(), RunInfoTableSpecVersion::v0())\n, protocol_start_time(\n      this,\n      \"protocol_start_time\",\n      arrow::timestamp(arrow::TimeUnit::MILLI, \"UTC\"),\n      RunInfoTableSpecVersion::v0())\n, sample_id(this, \"sample_id\", arrow::utf8(), RunInfoTableSpecVersion::v0())\n, sample_rate(this, \"sample_rate\", arrow::uint16(), RunInfoTableSpecVersion::v0())\n, sequencing_kit(this, \"sequencing_kit\", arrow::utf8(), RunInfoTableSpecVersion::v0())\n, sequencer_position(this, \"sequencer_position\", arrow::utf8(), RunInfoTableSpecVersion::v0())\n, sequencer_position_type(\n      this,\n      \"sequencer_position_type\",\n      arrow::utf8(),\n      RunInfoTableSpecVersion::v0())\n, software(this, \"software\", arrow::utf8(), RunInfoTableSpecVersion::v0())\n, system_name(this, \"system_name\", arrow::utf8(), RunInfoTableSpecVersion::v0())\n, system_type(this, \"system_type\", arrow::utf8(), RunInfoTableSpecVersion::v0())\n, tracking_id(\n      this,\n      \"tracking_id\",\n      arrow::map(arrow::utf8(), arrow::utf8()),\n      RunInfoTableSpecVersion::v0())\n{\n}\n\nTableSpecVersion RunInfoTableSchemaDescription::table_version_from_file_version(\n    Version file_version) const\n{\n    return RunInfoTableSpecVersion::latest();\n}\n\nResult<std::shared_ptr<RunInfoTableSchemaDescription const>> read_run_info_table_schema(\n    SchemaMetadataDescription const & schema_metadata,\n    std::shared_ptr<arrow::Schema> const & schema)\n{\n    auto result = std::make_shared<RunInfoTableSchemaDescription>();\n    ARROW_RETURN_NOT_OK(\n        RunInfoTableSchemaDescription::read_schema(result, schema_metadata, schema));\n\n    return result;\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/run_info_table_schema.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/schema_utils.h\"\n#include \"pod5_format/tuple_utils.h\"\n#include \"pod5_format/types.h\"\n\n#include <memory>\n#include <tuple>\n#include <vector>\n\nnamespace arrow {\nclass KeyValueMetadata;\nclass Schema;\nclass DataType;\nclass StructType;\n}  // namespace arrow\n\nnamespace pod5 {\n\nclass RunInfoTableSpecVersion {\npublic:\n    static TableSpecVersion v0() { return TableSpecVersion::first_version(); }\n\n    static TableSpecVersion latest() { return v0(); }\n};\n\nclass RunInfoTableSchemaDescription : public SchemaDescriptionBase {\npublic:\n    RunInfoTableSchemaDescription();\n\n    RunInfoTableSchemaDescription(RunInfoTableSchemaDescription const &) = delete;\n    RunInfoTableSchemaDescription & operator=(RunInfoTableSchemaDescription const &) = delete;\n\n    TableSpecVersion table_version_from_file_version(Version file_version) const override;\n\n    Field<0, arrow::StringArray> acquisition_id;\n    Field<1, arrow::TimestampArray> acquisition_start_time;\n    Field<2, arrow::Int16Array> adc_max;\n    Field<3, arrow::Int16Array> adc_min;\n    Field<4, arrow::MapArray> context_tags;\n    Field<5, arrow::StringArray> experiment_name;\n    Field<6, arrow::StringArray> flow_cell_id;\n    Field<7, arrow::StringArray> flow_cell_product_code;\n    Field<8, arrow::StringArray> protocol_name;\n    Field<9, arrow::StringArray> protocol_run_id;\n    Field<10, arrow::TimestampArray> protocol_start_time;\n    Field<11, arrow::StringArray> sample_id;\n    Field<12, arrow::UInt16Array> sample_rate;\n    Field<13, arrow::StringArray> sequencing_kit;\n    Field<14, arrow::StringArray> sequencer_position;\n    Field<15, arrow::StringArray> sequencer_position_type;\n    Field<16, arrow::StringArray> software;\n    Field<17, arrow::StringArray> system_name;\n    Field<18, arrow::StringArray> system_type;\n    Field<19, arrow::MapArray> tracking_id;\n\n    // Field Builders only for fields we write in newly generated files.\n    // Should not include fields which are removed in the latest version:\n    using FieldBuilders = FieldBuilder<\n        // V0 fields\n        decltype(acquisition_id),\n        decltype(acquisition_start_time),\n        decltype(adc_max),\n        decltype(adc_min),\n        decltype(context_tags),\n        decltype(experiment_name),\n        decltype(flow_cell_id),\n        decltype(flow_cell_product_code),\n        decltype(protocol_name),\n        decltype(protocol_run_id),\n        decltype(protocol_start_time),\n        decltype(sample_id),\n        decltype(sample_rate),\n        decltype(sequencing_kit),\n        decltype(sequencer_position),\n        decltype(sequencer_position_type),\n        decltype(software),\n        decltype(system_name),\n        decltype(system_type),\n        decltype(tracking_id)>;\n};\n\nPOD5_FORMAT_EXPORT Result<std::shared_ptr<RunInfoTableSchemaDescription const>>\nread_run_info_table_schema(\n    SchemaMetadataDescription const & schema_metadata,\n    std::shared_ptr<arrow::Schema> const &);\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/run_info_table_writer.cpp",
    "content": "#include \"pod5_format/run_info_table_writer.h\"\n\n#include \"pod5_format/file_output_stream.h\"\n#include \"pod5_format/internal/tracing/tracing.h\"\n#include \"pod5_format/read_table_utils.h\"\n\n#include <arrow/extension_type.h>\n#include <arrow/ipc/writer.h>\n#include <arrow/record_batch.h>\n#include <arrow/type.h>\n#include <arrow/util/compression.h>\n\nnamespace pod5 {\n\nRunInfoTableWriter::RunInfoTableWriter(\n    std::shared_ptr<arrow::ipc::RecordBatchWriter> && writer,\n    std::shared_ptr<arrow::Schema> && schema,\n    std::shared_ptr<RunInfoTableSchemaDescription> const & field_locations,\n    std::shared_ptr<FileOutputStream> const & output_stream,\n    std::size_t table_batch_size,\n    arrow::MemoryPool * pool)\n: m_schema(schema)\n, m_field_locations(field_locations)\n, m_output_stream{output_stream}\n, m_table_batch_size(table_batch_size)\n, m_writer(std::move(writer))\n, m_field_builders(m_field_locations, pool)\n{\n}\n\nRunInfoTableWriter::RunInfoTableWriter(RunInfoTableWriter && other) = default;\nRunInfoTableWriter & RunInfoTableWriter::operator=(RunInfoTableWriter &&) = default;\n\nRunInfoTableWriter::~RunInfoTableWriter()\n{\n    if (m_writer) {\n        (void)close();\n    }\n}\n\nResult<std::size_t> RunInfoTableWriter::add_run_info(RunInfoData const & run_info_data)\n{\n    POD5_TRACE_FUNCTION();\n    if (!m_writer) {\n        return Status::IOError(\"Writer terminated\");\n    }\n\n    ARROW_RETURN_NOT_OK(reserve_rows());\n\n    auto row_id = m_written_batched_row_count + m_current_batch_row_count;\n    ARROW_RETURN_NOT_OK(m_field_builders.append(\n        // V0 Fields\n        run_info_data.acquisition_id,\n        run_info_data.acquisition_start_time,\n        run_info_data.adc_max,\n        run_info_data.adc_min,\n        run_info_data.context_tags,\n        run_info_data.experiment_name,\n        run_info_data.flow_cell_id,\n        run_info_data.flow_cell_product_code,\n        run_info_data.protocol_name,\n        run_info_data.protocol_run_id,\n        run_info_data.protocol_start_time,\n        run_info_data.sample_id,\n        run_info_data.sample_rate,\n        run_info_data.sequencing_kit,\n        run_info_data.sequencer_position,\n        run_info_data.sequencer_position_type,\n        run_info_data.software,\n        run_info_data.system_name,\n        run_info_data.system_type,\n        run_info_data.tracking_id));\n\n    ++m_current_batch_row_count;\n\n    if (m_current_batch_row_count >= m_table_batch_size) {\n        ARROW_RETURN_NOT_OK(write_batch());\n    }\n    return row_id;\n}\n\nStatus RunInfoTableWriter::close()\n{\n    // Check for already closed\n    if (!m_writer) {\n        return Status::OK();\n    }\n\n    ARROW_RETURN_NOT_OK(write_batch());\n    ARROW_RETURN_NOT_OK(m_writer->Close());\n    m_writer = nullptr;\n    return Status::OK();\n}\n\nStatus RunInfoTableWriter::write_batch(arrow::RecordBatch const & record_batch)\n{\n    ARROW_RETURN_NOT_OK(m_writer->WriteRecordBatch(record_batch));\n    return m_output_stream->batch_complete();\n}\n\nStatus RunInfoTableWriter::write_batch()\n{\n    POD5_TRACE_FUNCTION();\n    if (m_current_batch_row_count == 0) {\n        return Status::OK();\n    }\n\n    if (!m_writer) {\n        return Status::IOError(\"Writer terminated\");\n    }\n\n    ARROW_ASSIGN_OR_RAISE(auto columns, m_field_builders.finish_columns());\n\n    auto const record_batch =\n        arrow::RecordBatch::Make(m_schema, m_current_batch_row_count, std::move(columns));\n\n    m_written_batched_row_count += m_current_batch_row_count;\n    m_current_batch_row_count = 0;\n\n    ARROW_RETURN_NOT_OK(m_writer->WriteRecordBatch(*record_batch));\n    return m_output_stream->batch_complete();\n}\n\nStatus RunInfoTableWriter::reserve_rows()\n{\n    // Only reserve if we have not already reserved (at the start of a batch)\n    if (m_current_batch_row_count > 0) {\n        return arrow::Status::OK();\n    }\n\n    return m_field_builders.reserve(m_table_batch_size);\n}\n\nResult<RunInfoTableWriter> make_run_info_table_writer(\n    std::shared_ptr<FileOutputStream> const & sink,\n    std::shared_ptr<arrow::KeyValueMetadata const> const & metadata,\n    std::size_t table_batch_size,\n    arrow::MemoryPool * pool)\n{\n    auto field_locations = std::make_shared<RunInfoTableSchemaDescription>();\n    auto schema = field_locations->make_writer_schema(metadata);\n\n    arrow::ipc::IpcWriteOptions options;\n    options.memory_pool = pool;\n\n    ARROW_ASSIGN_OR_RAISE(auto writer, arrow::ipc::MakeFileWriter(sink, schema, options, metadata));\n\n    auto run_info_table_writer = RunInfoTableWriter(\n        std::move(writer), std::move(schema), field_locations, sink, table_batch_size, pool);\n\n    return run_info_table_writer;\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/run_info_table_writer.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/run_info_table_schema.h\"\n#include \"pod5_format/schema_field_builder.h\"\n\n#include <arrow/array/builder_dict.h>\n#include <arrow/io/type_fwd.h>\n\nnamespace arrow {\nclass Schema;\n\nnamespace ipc {\nclass RecordBatchWriter;\n}\n}  // namespace arrow\n\nnamespace pod5 {\n\nclass FileOutputStream;\nclass RunInfoData;\n\nclass POD5_FORMAT_EXPORT RunInfoTableWriter {\npublic:\n    RunInfoTableWriter(\n        std::shared_ptr<arrow::ipc::RecordBatchWriter> && writer,\n        std::shared_ptr<arrow::Schema> && schema,\n        std::shared_ptr<RunInfoTableSchemaDescription> const & field_locations,\n        std::shared_ptr<FileOutputStream> const & output_stream,\n        std::size_t table_batch_size,\n        arrow::MemoryPool * pool);\n    RunInfoTableWriter(RunInfoTableWriter &&);\n    RunInfoTableWriter & operator=(RunInfoTableWriter &&);\n    RunInfoTableWriter(RunInfoTableWriter const &) = delete;\n    RunInfoTableWriter & operator=(RunInfoTableWriter const &) = delete;\n    ~RunInfoTableWriter();\n\n    /// \\brief Add a run info to the table, adding to the current batch.\n    /// \\param run_info_data The run info data to add.\n    /// \\returns The row index of the inserted read, or a status on failure.\n    Result<std::size_t> add_run_info(RunInfoData const & run_info_data);\n\n    /// \\brief Close this writer, signaling no further data will be written to the writer.\n    Status close();\n\n    /// \\brief Reserve space for future row writes, called automatically when a flush occurs.\n    Status reserve_rows();\n\n    /// \\brief Find the schema for the table\n    std::shared_ptr<arrow::Schema> const & schema() const { return m_schema; }\n\n    /// \\brief Flush passed data into the writer as a record batch.\n    Status write_batch(arrow::RecordBatch const &);\n\nprivate:\n    /// \\brief Flush buffered data into the writer as a record batch.\n    Status write_batch();\n\n    std::shared_ptr<arrow::Schema> m_schema;\n    std::shared_ptr<RunInfoTableSchemaDescription> m_field_locations;\n    std::shared_ptr<FileOutputStream> m_output_stream;\n    std::size_t m_table_batch_size;\n\n    std::shared_ptr<arrow::ipc::RecordBatchWriter> m_writer;\n\n    RunInfoTableSchemaDescription::FieldBuilders m_field_builders;\n\n    std::size_t m_written_batched_row_count = 0;\n    std::size_t m_current_batch_row_count = 0;\n};\n\n/// \\brief Make a new writer for a read table.\n/// \\param sink Sink to be used for output of the table.\n/// \\param metadata Metadata to be applied to the table schema.\n/// \\param table_batch_size The size of each batch written for the table.\n/// \\param pool Pool to be used for building table in memory.\n/// \\returns The writer for the new table.\nPOD5_FORMAT_EXPORT Result<RunInfoTableWriter> make_run_info_table_writer(\n    std::shared_ptr<FileOutputStream> const & sink,\n    std::shared_ptr<arrow::KeyValueMetadata const> const & metadata,\n    std::size_t table_batch_size,\n    arrow::MemoryPool * pool);\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/schema_field_builder.h",
    "content": "#pragma once\n\n#include \"pod5_format/dictionary_writer.h\"\n#include \"pod5_format/read_table_schema.h\"\n\n#include <arrow/array/builder_binary.h>\n#include <arrow/array/builder_nested.h>\n#include <arrow/array/builder_primitive.h>\n\nnamespace pod5 {\n\nclass DictionaryWriter;\n\nnamespace detail {\ntemplate <typename ArrayType>\nclass BuilderHelper;\ntemplate <typename ArrayType, typename ElementArrayType>\nclass ListBuilderHelper;\n\ntemplate <>\nclass BuilderHelper<UuidArray> : public arrow::FixedSizeBinaryBuilder {\npublic:\n    BuilderHelper(std::shared_ptr<arrow::DataType> const & uuid_type, arrow::MemoryPool * pool)\n    : arrow::FixedSizeBinaryBuilder(find_storage_type(uuid_type), pool)\n    {\n        assert(byte_width() == 16);\n    }\n\n    static std::shared_ptr<arrow::DataType> find_storage_type(\n        std::shared_ptr<arrow::DataType> const & uuid_type)\n    {\n        assert(uuid_type->id() == arrow::Type::EXTENSION);\n        auto const & uuid_extension = static_cast<arrow::ExtensionType const &>(*uuid_type);\n        return uuid_extension.storage_type();\n    }\n\n    arrow::Status Append(Uuid const & uuid)\n    {\n        return static_cast<arrow::FixedSizeBinaryBuilder *>(this)->Append(uuid.data());\n    }\n};\n\ntemplate <>\nclass BuilderHelper<arrow::FloatArray> : public arrow::FloatBuilder {\npublic:\n    BuilderHelper(std::shared_ptr<arrow::DataType> const &, arrow::MemoryPool * pool)\n    : arrow::FloatBuilder(pool)\n    {\n    }\n};\n\ntemplate <>\nclass BuilderHelper<arrow::UInt8Array> : public arrow::UInt8Builder {\npublic:\n    BuilderHelper(std::shared_ptr<arrow::DataType> const &, arrow::MemoryPool * pool)\n    : arrow::UInt8Builder(pool)\n    {\n    }\n};\n\ntemplate <>\nclass BuilderHelper<arrow::UInt16Array> : public arrow::UInt16Builder {\npublic:\n    BuilderHelper(std::shared_ptr<arrow::DataType> const &, arrow::MemoryPool * pool)\n    : arrow::UInt16Builder(pool)\n    {\n    }\n};\n\ntemplate <>\nclass BuilderHelper<arrow::Int16Array> : public arrow::Int16Builder {\npublic:\n    BuilderHelper(std::shared_ptr<arrow::DataType> const &, arrow::MemoryPool * pool)\n    : arrow::Int16Builder(pool)\n    {\n    }\n};\n\ntemplate <>\nclass BuilderHelper<arrow::UInt32Array> : public arrow::UInt32Builder {\npublic:\n    BuilderHelper(std::shared_ptr<arrow::DataType> const &, arrow::MemoryPool * pool)\n    : arrow::UInt32Builder(pool)\n    {\n    }\n};\n\ntemplate <>\nclass BuilderHelper<arrow::UInt64Array> : public arrow::UInt64Builder {\npublic:\n    BuilderHelper(std::shared_ptr<arrow::DataType> const &, arrow::MemoryPool * pool)\n    : arrow::UInt64Builder(pool)\n    {\n    }\n};\n\ntemplate <>\nclass BuilderHelper<arrow::BooleanArray> : public arrow::BooleanBuilder {\npublic:\n    BuilderHelper(std::shared_ptr<arrow::DataType> const &, arrow::MemoryPool * pool)\n    : arrow::BooleanBuilder(pool)\n    {\n    }\n};\n\ntemplate <>\nclass BuilderHelper<arrow::NumericArray<arrow::TimestampType>> : public arrow::TimestampBuilder {\npublic:\n    BuilderHelper(std::shared_ptr<arrow::DataType> const & type, arrow::MemoryPool * pool)\n    : arrow::TimestampBuilder(type, pool)\n    {\n    }\n};\n\ntemplate <>\nclass BuilderHelper<arrow::StringArray> : public arrow::StringBuilder {\npublic:\n    BuilderHelper(std::shared_ptr<arrow::DataType> const &, arrow::MemoryPool * pool)\n    : arrow::StringBuilder(pool)\n    {\n    }\n};\n\ntemplate <>\nclass BuilderHelper<arrow::MapArray> {\npublic:\n    BuilderHelper(std::shared_ptr<arrow::DataType> const &, arrow::MemoryPool * pool)\n    : m_key_builder(std::make_shared<arrow::StringBuilder>(pool))\n    , m_item_builder(std::make_shared<arrow::StringBuilder>(pool))\n    , m_map_builder(pool, m_key_builder, m_item_builder)\n    {\n    }\n\n    arrow::Status Finish(std::shared_ptr<arrow::Array> * dest)\n    {\n        return m_map_builder.Finish(dest);\n    }\n\n    arrow::Status Reserve(std::size_t rows) { return m_map_builder.Reserve(rows); }\n\n    arrow::Status Append(std::vector<std::pair<std::string, std::string>> const & items)\n    {\n        ARROW_RETURN_NOT_OK(m_map_builder.Append());  // start new slot\n        for (auto const & pair : items) {\n            ARROW_RETURN_NOT_OK(m_key_builder->Append(pair.first));\n            ARROW_RETURN_NOT_OK(m_item_builder->Append(pair.second));\n        }\n        return arrow::Status::OK();\n    }\n\nprivate:\n    std::shared_ptr<arrow::StringBuilder> m_key_builder;\n    std::shared_ptr<arrow::StringBuilder> m_item_builder;\n    arrow::MapBuilder m_map_builder;\n};\n\ntemplate <>\nclass BuilderHelper<arrow::DictionaryArray> : public arrow::Int16Builder {\npublic:\n    BuilderHelper(std::shared_ptr<arrow::DataType> const &, arrow::MemoryPool * pool)\n    : arrow::Int16Builder(pool)\n    {\n    }\n\n    void set_dict_writer(std::shared_ptr<DictionaryWriter> const & writer)\n    {\n        m_dict_writer = writer;\n    }\n\n    arrow::Status Finish(std::shared_ptr<arrow::Array> * dest)\n    {\n        arrow::Int16Builder * index_builder = this;\n        ARROW_ASSIGN_OR_RAISE(auto indices, index_builder->Finish());\n        ARROW_ASSIGN_OR_RAISE(*dest, m_dict_writer->build_dictionary_array(indices));\n        return arrow::Status::OK();\n    }\n\nprivate:\n    std::shared_ptr<DictionaryWriter> m_dict_writer;\n};\n\ntemplate <typename ElementArrayType>\nclass ListBuilderHelper<arrow::ListArray, ElementArrayType> {\npublic:\n    ListBuilderHelper(std::shared_ptr<arrow::DataType> const &, arrow::MemoryPool * pool)\n    : m_array_builder(std::make_shared<BuilderHelper<ElementArrayType>>(nullptr, pool))\n    , m_builder(std::make_unique<arrow::ListBuilder>(pool, m_array_builder))\n    {\n    }\n\n    arrow::Status Reserve(std::size_t rows)\n    {\n        ARROW_RETURN_NOT_OK(m_builder->Reserve(rows));\n        return m_array_builder->Reserve(rows);\n    }\n\n    arrow::Status Finish(std::shared_ptr<arrow::Array> * dest) { return m_builder->Finish(dest); }\n\n    template <typename Items>\n    arrow::Status Append(Items const & items)\n    {\n        ARROW_RETURN_NOT_OK(m_builder->Append());  // start new slot\n        return m_array_builder->AppendValues(items.data(), items.size());\n    }\n\nprivate:\n    std::shared_ptr<BuilderHelper<ElementArrayType>> m_array_builder;\n    std::unique_ptr<arrow::ListBuilder> m_builder;\n};\n\n}  // namespace detail\n\ntemplate <typename... Args>\nclass FieldBuilder {\npublic:\n    using BuilderTuple = std::tuple<typename Args::BuilderType...>;\n\n    template <typename SchamaDescription>\n    FieldBuilder(std::shared_ptr<SchamaDescription> const & desc_base, arrow::MemoryPool * pool)\n    : m_builders(\n          typename Args::BuilderType(\n              desc_base->fields()[Args::WriteIndex::value]->datatype(),\n              pool)...)\n    {\n    }\n\n    template <typename FieldType>\n    std::tuple_element_t<FieldType::WriteIndex::value, BuilderTuple> & get_builder(FieldType)\n    {\n        return std::get<FieldType::WriteIndex::value>(m_builders);\n    }\n\n    arrow::Result<std::vector<std::shared_ptr<arrow::Array>>> finish_columns()\n    {\n        arrow::Status result;\n        std::vector<std::shared_ptr<arrow::Array>> columns;\n        columns.resize(std::tuple_size<decltype(m_builders)>::value);\n\n        detail::for_each_in_tuple(m_builders, [&](auto & element, std::size_t index) {\n            if (result.ok()) {\n                result = element.Finish(&columns[index]);\n                assert(columns[index] || !result.ok());\n            }\n        });\n\n        if (!result.ok()) {\n            return result;\n        }\n\n        return columns;\n    }\n\n    arrow::Status reserve(std::size_t row_count)\n    {\n        arrow::Status result;\n        detail::for_each_in_tuple(m_builders, [&](auto & element, std::size_t _) {\n            if (result.ok()) {\n                result = element.Reserve(row_count);\n            }\n        });\n        return result;\n    }\n\n    template <typename... AppendArgs>\n    arrow::Status append(AppendArgs const &... args)\n    {\n        auto args_list = std::forward_as_tuple(args...);\n\n        arrow::Status result;\n        for_each_in_tuple_zipped(\n            m_builders, args_list, [&](auto & builder, auto & item, std::size_t _) {\n                if (result.ok()) {\n                    result = builder.Append(item);\n                }\n            });\n        return result;\n    }\n\nprivate:\n    BuilderTuple m_builders;\n};\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/schema_metadata.cpp",
    "content": "#include \"pod5_format/schema_metadata.h\"\n\n#include \"pod5_format/uuid.h\"\n#include \"pod5_format/version.h\"\n\n#include <arrow/util/key_value_metadata.h>\n\nnamespace pod5 {\n\nResult<Version> parse_version_number(std::string const & ver)\n{\n    std::uint16_t components[3];\n    std::size_t component_index = 0;\n    std::size_t last_char_index = 0;\n    std::size_t char_index = 0;\n\n    auto parse_component = [&](std::size_t last_char_index, std::size_t char_index) {\n        auto const component_str =\n            std::string(ver.data() + last_char_index, ver.data() + char_index);\n\n        std::size_t pos = 0;\n        int val = std::stoi(component_str, &pos);\n\n        if (pos != (char_index - last_char_index)) {\n            throw std::runtime_error(\"Invalid remaining characters after version number\");\n        }\n\n        return val;\n    };\n\n    try {\n        while (char_index < ver.size()) {\n            if (ver[char_index] == '.') {\n                if (component_index > 3) {\n                    return Status::Invalid(\"Invalid component count\");\n                }\n                components[component_index] = parse_component(last_char_index, char_index);\n\n                last_char_index = char_index + 1;\n                component_index += 1;\n            }\n            char_index += 1;\n        }\n\n        // extract the final component\n        if (component_index != 2) {\n            return Status::Invalid(\"Invalid component count\");\n        }\n        components[2] = parse_component(last_char_index, char_index);\n    } catch (std::exception const & e) {\n        return Status::Invalid(e.what());\n    }\n\n    return Version{components[0], components[1], components[2]};\n}\n\nVersion current_build_version_number()\n{\n    return Version(Pod5MajorVersion, Pod5MinorVersion, Pod5RevVersion);\n}\n\nResult<std::shared_ptr<arrow::KeyValueMetadata const>> make_schema_key_value_metadata(\n    SchemaMetadataDescription const & schema_metadata)\n{\n    if (schema_metadata.writing_software.empty()) {\n        return Status::Invalid(\"Expected writing_software to be specified for metadata\");\n    }\n\n    if (schema_metadata.writing_pod5_version == Version{}) {\n        return Status::Invalid(\"Expected writing_pod5_version to be specified for metadata\");\n    }\n\n    if (schema_metadata.file_identifier == Uuid{}) {\n        return Status::Invalid(\"Expected file_identifier to be specified for metadata\");\n    }\n\n    return arrow::KeyValueMetadata::Make(\n        {\"MINKNOW:file_identifier\", \"MINKNOW:software\", \"MINKNOW:pod5_version\"},\n        {to_string(schema_metadata.file_identifier),\n         schema_metadata.writing_software,\n         schema_metadata.writing_pod5_version.to_string()});\n}\n\nResult<SchemaMetadataDescription> read_schema_key_value_metadata(\n    std::shared_ptr<arrow::KeyValueMetadata const> const & key_value_metadata)\n{\n    ARROW_ASSIGN_OR_RAISE(\n        auto file_identifier_str, key_value_metadata->Get(\"MINKNOW:file_identifier\"));\n    ARROW_ASSIGN_OR_RAISE(auto software_str, key_value_metadata->Get(\"MINKNOW:software\"));\n    ARROW_ASSIGN_OR_RAISE(auto pod5_version_str, key_value_metadata->Get(\"MINKNOW:pod5_version\"));\n    ARROW_ASSIGN_OR_RAISE(auto pod5_version, parse_version_number(pod5_version_str));\n\n    auto const file_identifier = Uuid::from_string(file_identifier_str);\n    if (!file_identifier) {\n        return Status::IOError(\n            \"Schema file_identifier metadata not uuid form: '\", file_identifier_str, \"'\");\n    }\n\n    return SchemaMetadataDescription{*file_identifier, software_str, pod5_version};\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/schema_metadata.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/uuid.h\"\n\n#include <memory>\n#include <string>\n#include <tuple>\n\nnamespace arrow {\nclass KeyValueMetadata;\n}\n\nnamespace pod5 {\n\nclass Version {\npublic:\n    Version() : m_version(0, 0, 0) {}\n\n    Version(std::uint16_t major, std::uint16_t minor, std::uint16_t revision)\n    : m_version(major, minor, revision)\n    {\n    }\n\n    bool operator<(Version const & in) const { return m_version < in.m_version; }\n\n    bool operator>(Version const & in) const { return m_version > in.m_version; }\n\n    bool operator==(Version const & in) const { return m_version == in.m_version; }\n\n    bool operator!=(Version const & in) const { return m_version != in.m_version; }\n\n    std::string to_string() const\n    {\n        return std::to_string(std::get<0>(m_version)) + \".\" + std::to_string(std::get<1>(m_version))\n               + \".\" + std::to_string(std::get<2>(m_version));\n    }\n\n    std::uint16_t major_version() const { return std::get<0>(m_version); }\n\n    std::uint16_t minor_version() const { return std::get<1>(m_version); }\n\n    std::uint16_t revision_version() const { return std::get<2>(m_version); }\n\nprivate:\n    std::tuple<std::uint16_t, std::uint16_t, std::uint16_t> m_version;\n};\n\nPOD5_FORMAT_EXPORT Result<Version> parse_version_number(std::string const & ver);\nPOD5_FORMAT_EXPORT Version current_build_version_number();\n\nstruct SchemaMetadataDescription {\n    Uuid file_identifier;\n    std::string writing_software;\n    Version writing_pod5_version;\n};\n\nPOD5_FORMAT_EXPORT Result<std::shared_ptr<arrow::KeyValueMetadata const>>\nmake_schema_key_value_metadata(SchemaMetadataDescription const & schema_metadata);\n\nPOD5_FORMAT_EXPORT Result<SchemaMetadataDescription> read_schema_key_value_metadata(\n    std::shared_ptr<arrow::KeyValueMetadata const> const & key_value_metadata);\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/schema_utils.cpp",
    "content": "#include \"pod5_format/schema_utils.h\"\n\nnamespace pod5 {\n\n/// \\brief Make a new schema for a read table to be written (will only contain fields which are written in the latest version).\n/// \\param metadata Metadata to be applied to the schema.\n/// \\returns The schema for a read table.\nstd::shared_ptr<arrow::Schema> SchemaDescriptionBase::make_writer_schema(\n    std::shared_ptr<arrow::KeyValueMetadata const> const & metadata) const\n{\n    auto const latest_version = latest_table_version();\n    arrow::FieldVector writer_fields;\n    for (auto & field : fields()) {\n        if (field->removed_table_spec_version() > latest_version) {\n            writer_fields.emplace_back(arrow::field(field->name(), field->datatype()));\n        }\n    }\n    return arrow::schema(writer_fields, metadata);\n}\n\nStatus SchemaDescriptionBase::read_schema(\n    std::shared_ptr<SchemaDescriptionBase> dest_schema,\n    SchemaMetadataDescription const & schema_metadata,\n    std::shared_ptr<arrow::Schema> const & schema)\n{\n    dest_schema->m_table_spec_version =\n        dest_schema->table_version_from_file_version(schema_metadata.writing_pod5_version);\n\n    for (auto & field : dest_schema->fields()) {\n        if (dest_schema->table_version() < field->added_table_spec_version()\n            || dest_schema->table_version() >= field->removed_table_spec_version())\n        {\n            continue;\n        }\n\n        auto const & datatype = field->datatype();\n        int field_index = 0;\n        if (datatype->id() == arrow::Type::DICTIONARY) {\n            auto const & dict_type = static_cast<arrow::DictionaryType const &>(*datatype);\n            if (dict_type.value_type()->id() == arrow::Type::STRUCT) {\n                std::shared_ptr<arrow::StructType> value_type;\n                ARROW_ASSIGN_OR_RAISE(\n                    field_index,\n                    find_dict_field(schema, field->name().c_str(), arrow::int16(), &value_type));\n            } else {\n                std::shared_ptr<arrow::StringType> value_type;\n                ARROW_ASSIGN_OR_RAISE(\n                    field_index,\n                    find_dict_field(schema, field->name().c_str(), arrow::int16(), &value_type));\n            }\n        } else {\n            ARROW_ASSIGN_OR_RAISE(field_index, find_field(schema, field->name().c_str(), datatype));\n        }\n        field->set_field_index(field_index);\n    }\n\n    return arrow::Status::OK();\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/schema_utils.h",
    "content": "#pragma once\n\n#include \"pod5_format/schema_metadata.h\"\n\n#include <arrow/record_batch.h>\n#include <arrow/type.h>\n\nnamespace pod5 {\n\ninline arrow::Result<int> find_field_untyped(\n    std::shared_ptr<arrow::Schema> const & schema,\n    char const * name)\n{\n    auto const field_idx = schema->GetFieldIndex(name);\n    if (field_idx == -1) {\n        return Status::TypeError(\"Schema missing field '\", name, \"'\");\n    }\n\n    return field_idx;\n}\n\ninline arrow::Result<int> find_field(\n    std::shared_ptr<arrow::Schema> const & schema,\n    char const * name,\n    std::shared_ptr<arrow::DataType> const & expected_data_type)\n{\n    ARROW_ASSIGN_OR_RAISE(auto field_idx, find_field_untyped(schema, name));\n\n    auto const field = schema->field(field_idx);\n    auto const type = field->type();\n\n    if (!type->Equals(expected_data_type)) {\n        return Status::TypeError(\n            \"Schema field '\", name, \"' is incorrect type: '\", type->name(), \"'\");\n    }\n\n    return field_idx;\n}\n\ntemplate <typename ValueType>\ninline arrow::Result<int> find_dict_field(\n    std::shared_ptr<arrow::Schema> const & schema,\n    char const * name,\n    std::shared_ptr<arrow::DataType> const & index_type,\n    std::shared_ptr<ValueType> * value_type)\n{\n    ARROW_ASSIGN_OR_RAISE(auto field_idx, find_field_untyped(schema, name));\n\n    auto const field = schema->field(field_idx);\n    auto const type = std::dynamic_pointer_cast<arrow::DictionaryType>(field->type());\n    if (!type) {\n        return Status::TypeError(\"Dictionary field was unexpected type: \", field->type()->name());\n    }\n\n    if (!type->index_type()->Equals(index_type)) {\n        return Status::TypeError(\n            \"Schema field '\", name, \"' is incorrect type: '\", type->name(), \"'\");\n    }\n\n    *value_type = std::dynamic_pointer_cast<ValueType>(type->value_type());\n    if (!*value_type) {\n        return Status::TypeError(\n            \"Dictionary value was unexpected type: \", type->value_type()->name());\n    }\n    return field_idx;\n}\n\ntemplate <typename FieldType>\nstd::shared_ptr<typename FieldType::ArrayType> find_column(\n    std::shared_ptr<arrow::RecordBatch> const & batch,\n    FieldType const & field)\n{\n    auto field_base = batch->column(field.field_index());\n    return std::static_pointer_cast<typename FieldType::ArrayType>(std::move(field_base));\n}\n\nclass FieldBase;\n\nenum class SpecialFieldValues : int {\n    InvalidField = -1,\n};\n\nclass TableSpecVersion {\npublic:\n    using UnderlyingType = std::uint8_t;\n\n    TableSpecVersion() : m_version(std::numeric_limits<UnderlyingType>::max()) {}\n\n    static TableSpecVersion first_version() { return TableSpecVersion(0); }\n\n    static TableSpecVersion unknown_version() { return TableSpecVersion(); }\n\n    static TableSpecVersion at_version(UnderlyingType version) { return TableSpecVersion(version); }\n\n    UnderlyingType as_int() const { return m_version; }\n\n    bool operator<(TableSpecVersion const & other) const { return m_version < other.m_version; }\n\n    bool operator>(TableSpecVersion const & other) const { return m_version > other.m_version; }\n\n    bool operator<=(TableSpecVersion const & other) const { return m_version <= other.m_version; }\n\n    bool operator>=(TableSpecVersion const & other) const { return m_version >= other.m_version; }\n\nprivate:\n    TableSpecVersion(UnderlyingType version) : m_version(version) {}\n\n    UnderlyingType m_version;\n};\n\nclass SchemaDescriptionBase {\npublic:\n    SchemaDescriptionBase(TableSpecVersion version) : m_table_spec_version(version) {}\n\n    virtual ~SchemaDescriptionBase() = default;\n\n    void add_field(FieldBase * field) { m_fields.push_back(field); }\n\n    std::vector<FieldBase *> const & fields() { return m_fields; }\n\n    std::vector<FieldBase const *> const & fields() const\n    {\n        return reinterpret_cast<std::vector<FieldBase const *> const &>(m_fields);\n    }\n\n    TableSpecVersion latest_table_version() const\n    {\n        return table_version_from_file_version(current_build_version_number());\n    }\n\n    virtual TableSpecVersion table_version_from_file_version(Version file_version) const = 0;\n\n    TableSpecVersion table_version() const { return m_table_spec_version; }\n\n    /// \\brief Make a new schema for a read table to be written (will only contain fields which are written in the latest version).\n    /// \\param metadata Metadata to be applied to the schema.\n    /// \\returns The schema for a read table.\n    std::shared_ptr<arrow::Schema> make_writer_schema(\n        std::shared_ptr<arrow::KeyValueMetadata const> const & metadata) const;\n\n    static Status read_schema(\n        std::shared_ptr<SchemaDescriptionBase> dest_schema,\n        SchemaMetadataDescription const & schema_metadata,\n        std::shared_ptr<arrow::Schema> const & schema);\n\nprivate:\n    std::vector<FieldBase *> m_fields;\n    TableSpecVersion m_table_spec_version;\n};\n\nnamespace detail {\ntemplate <typename ArrayType>\nclass BuilderHelper;\ntemplate <typename ArrayType, typename ElementArrayType>\nclass ListBuilderHelper;\n}  // namespace detail\n\nclass FieldBase {\npublic:\n    FieldBase(\n        SchemaDescriptionBase * owner,\n        int field_index,\n        std::string name,\n        std::shared_ptr<arrow::DataType> const & datatype,\n        TableSpecVersion added_table_spec_version = TableSpecVersion::first_version(),\n        TableSpecVersion removed_table_spec_version = TableSpecVersion::unknown_version())\n    : m_name(name)\n    , m_datatype(datatype)\n    , m_field_index(field_index)\n    , m_added_table_spec_version(added_table_spec_version)\n    , m_removed_table_spec_version(removed_table_spec_version)\n    {\n        owner->add_field(this);\n    }\n\n    std::string const & name() const { return m_name; }\n\n    std::shared_ptr<arrow::DataType> const & datatype() const { return m_datatype; }\n\n    int field_index() const { return m_field_index; }\n\n    TableSpecVersion added_table_spec_version() const { return m_added_table_spec_version; }\n\n    TableSpecVersion removed_table_spec_version() const { return m_removed_table_spec_version; }\n\n    void set_field_index(int index) { m_field_index = index; }\n\n    bool found_field() const { return m_field_index != (int)SpecialFieldValues::InvalidField; }\n\nprivate:\n    std::string m_name;\n    std::shared_ptr<arrow::DataType> m_datatype;\n    int m_field_index = (int)SpecialFieldValues::InvalidField;\n    TableSpecVersion m_added_table_spec_version;\n    TableSpecVersion m_removed_table_spec_version;\n};\n\ntemplate <int WriteIndex_, typename ArrayType_>\nstruct Field : public FieldBase {\n    using WriteIndex = std::integral_constant<int, WriteIndex_>;\n    using ArrayType = ArrayType_;\n    using BuilderType = detail::BuilderHelper<ArrayType>;\n\n    Field(\n        SchemaDescriptionBase * owner,\n        std::string name,\n        std::shared_ptr<arrow::DataType> const & datatype,\n        TableSpecVersion added_table_spec_version = TableSpecVersion::first_version(),\n        TableSpecVersion removed_table_spec_version = TableSpecVersion::unknown_version())\n    : FieldBase(\n          owner,\n          WriteIndex::value,\n          name,\n          datatype,\n          added_table_spec_version,\n          removed_table_spec_version)\n    {\n    }\n};\n\ntemplate <int WriteIndex_, typename ArrayType_, typename ElementType_>\nstruct ListField : public Field<WriteIndex_, ArrayType_> {\n    using ElementType = ElementType_;\n    using BuilderType = detail::ListBuilderHelper<ArrayType_, ElementType>;\n\n    ListField(\n        SchemaDescriptionBase * owner,\n        std::string name,\n        std::shared_ptr<arrow::DataType> const & datatype,\n        TableSpecVersion added_table_spec_version = TableSpecVersion::first_version(),\n        TableSpecVersion removed_table_spec_version = TableSpecVersion::unknown_version())\n    : Field<WriteIndex_, ArrayType_>(\n          owner,\n          name,\n          datatype,\n          added_table_spec_version,\n          removed_table_spec_version)\n    {\n    }\n};\n\ntemplate <typename... Args>\nclass FieldBuilder;\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/signal_builder.h",
    "content": "#pragma once\n\n#include \"pod5_format/expandable_buffer.h\"\n#include \"pod5_format/signal_compression.h\"\n#include \"pod5_format/signal_table_utils.h\"\n#include \"pod5_format/types.h\"\n\n#include <arrow/array/builder_nested.h>\n#include <arrow/array/builder_primitive.h>\n#include <arrow/array/util.h>\n\n#include <variant>\n\nnamespace pod5 {\n\nstruct UncompressedSignalBuilder {\n    std::shared_ptr<arrow::Int16Builder> signal_data_builder;\n    std::unique_ptr<arrow::LargeListBuilder> signal_builder;\n};\n\nstruct VbzSignalBuilder {\n    ExpandableBuffer<std::int64_t> offset_values;\n    ExpandableBuffer<std::uint8_t> data_values;\n};\n\nusing SignalBuilderVariant = std::variant<UncompressedSignalBuilder, VbzSignalBuilder>;\n\ninline arrow::Result<SignalBuilderVariant> make_signal_builder(\n    SignalType compression_type,\n    arrow::MemoryPool * pool)\n{\n    if (compression_type == SignalType::UncompressedSignal) {\n        auto signal_array_builder = std::make_shared<arrow::Int16Builder>(pool);\n        return UncompressedSignalBuilder{\n            signal_array_builder,\n            std::make_unique<arrow::LargeListBuilder>(pool, signal_array_builder),\n        };\n    } else {\n        VbzSignalBuilder vbz_builder;\n        ARROW_RETURN_NOT_OK(vbz_builder.offset_values.init_buffer(pool));\n        ARROW_RETURN_NOT_OK(vbz_builder.data_values.init_buffer(pool));\n        return vbz_builder;\n    }\n}\n\nnamespace visitors {\nclass reserve_rows {\npublic:\n    reserve_rows(std::size_t row_count, std::size_t approx_read_samples)\n    : m_row_count(row_count)\n    , m_approx_read_samples(approx_read_samples)\n    {\n    }\n\n    Status operator()(UncompressedSignalBuilder & builder) const\n    {\n        ARROW_RETURN_NOT_OK(builder.signal_builder->Reserve(m_row_count));\n        return builder.signal_data_builder->Reserve(m_row_count * m_approx_read_samples);\n    }\n\n    Status operator()(VbzSignalBuilder & builder) const\n    {\n        ARROW_RETURN_NOT_OK(builder.offset_values.reserve(m_row_count + 1));\n        return builder.data_values.reserve(m_row_count * m_approx_read_samples);\n    }\n\n    std::size_t m_row_count;\n    std::size_t m_approx_read_samples;\n};\n\nclass append_pre_compressed_signal {\npublic:\n    append_pre_compressed_signal(gsl::span<std::uint8_t const> const & signal) : m_signal(signal) {}\n\n    Status operator()(UncompressedSignalBuilder & builder) const\n    {\n        ARROW_RETURN_NOT_OK(builder.signal_builder->Append());  // start new slot\n\n        auto as_uncompressed = m_signal.as_span<std::int16_t const>();\n        return builder.signal_data_builder->AppendValues(\n            as_uncompressed.data(), as_uncompressed.size());\n    }\n\n    Status operator()(VbzSignalBuilder & builder) const\n    {\n        ARROW_RETURN_NOT_OK(builder.offset_values.append(builder.data_values.size()));\n        return builder.data_values.append_array(m_signal);\n    }\n\n    gsl::span<std::uint8_t const> m_signal;\n};\n\nclass append_signal {\npublic:\n    append_signal(gsl::span<std::int16_t const> const & signal, arrow::MemoryPool * pool)\n    : m_signal(signal)\n    , m_pool(pool)\n    {\n    }\n\n    Status operator()(UncompressedSignalBuilder & builder) const\n    {\n        ARROW_RETURN_NOT_OK(builder.signal_builder->Append());  // start new slot\n        return builder.signal_data_builder->AppendValues(m_signal.data(), m_signal.size());\n    }\n\n    Status operator()(VbzSignalBuilder & builder) const\n    {\n        ARROW_RETURN_NOT_OK(builder.offset_values.append(builder.data_values.size()));\n\n        ARROW_ASSIGN_OR_RAISE(auto const max_size, compressed_signal_max_size(m_signal.size()));\n\n        // Compress the signal in place into our buffer.\n        return builder.data_values.append(\n            max_size, [&](gsl::span<std::uint8_t> buffer) -> arrow::Result<std::size_t> {\n                return compress_signal(m_signal, m_pool, buffer);\n            });\n    }\n\n    gsl::span<std::int16_t const> m_signal;\n    arrow::MemoryPool * m_pool;\n};\n\nclass finish_column {\npublic:\n    finish_column(std::shared_ptr<arrow::Array> * dest) : m_dest(dest) {}\n\n    Status operator()(UncompressedSignalBuilder & builder) const\n    {\n        return builder.signal_builder->Finish(m_dest);\n    }\n\n    Status operator()(VbzSignalBuilder & builder) const\n    {\n        auto offsets_copy = builder.offset_values;\n        ARROW_RETURN_NOT_OK(builder.offset_values.clear());\n\n        auto const value_data = builder.data_values.get_buffer();\n        ARROW_RETURN_NOT_OK(builder.data_values.clear());\n\n        auto const length = offsets_copy.size();\n\n        // Write final offset (values length)\n        ARROW_RETURN_NOT_OK(offsets_copy.append(value_data->size()));\n        auto const offsets = offsets_copy.get_buffer();\n\n        std::shared_ptr<arrow::Buffer> null_bitmap;\n\n        *m_dest = arrow::MakeArray(\n            arrow::ArrayData::Make(vbz_signal(), length, {null_bitmap, offsets, value_data}, 0, 0));\n\n        return arrow::Status::OK();\n    }\n\n    std::shared_ptr<arrow::Array> * m_dest;\n};\n\n}  // namespace visitors\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/signal_compression.cpp",
    "content": "#include \"pod5_format/signal_compression.h\"\n\n#include \"pod5_format/svb16/decode.hpp\"\n#include \"pod5_format/svb16/encode.hpp\"\n\n#include <arrow/buffer.h>\n#include <arrow/util/io_util.h>\n#include <zstd.h>\n\n#include <cassert>\n#include <limits>\n\nnamespace pod5 {\n\nnamespace {\n\n// SVB is designed around 32 bit sizes, so that's the maximum uncompressed samples allowed.\nconstexpr std::size_t max_uncompressed_samples = std::numeric_limits<std::uint32_t>::max();\n\nclass DecompressContext {\n    struct DCtxDeleter {\n        void operator()(ZSTD_DCtx * ctx) { ZSTD_freeDCtx(ctx); }\n    };\n\n    std::unique_ptr<ZSTD_DCtx, DCtxDeleter> m_context;\n\npublic:\n    DecompressContext() { m_context.reset(ZSTD_createDCtx()); }\n\n    ZSTD_DCtx * get() { return m_context.get(); }\n\n    explicit operator bool() const { return static_cast<bool>(m_context); }\n};\n\n}  // namespace\n\narrow::Result<std::size_t> compressed_signal_max_size(std::size_t sample_count)\n{\n    if (sample_count > max_uncompressed_samples) {\n        return arrow::Status::Invalid(\n            sample_count, \" samples exceeds max of \", max_uncompressed_samples);\n    }\n\n    auto const max_svb_size = svb16_max_encoded_length(sample_count);\n    auto const zstd_compressed_max_size = ZSTD_compressBound(max_svb_size);\n    if (ZSTD_isError(zstd_compressed_max_size)) {\n        return pod5::Status::Invalid(\n            sample_count,\n            \" samples exceeds zstd limit: (\",\n            zstd_compressed_max_size,\n            \" \",\n            ZSTD_getErrorName(zstd_compressed_max_size),\n            \")\");\n    }\n\n    return zstd_compressed_max_size;\n}\n\narrow::Result<std::size_t> compress_signal(\n    gsl::span<SampleType const> samples,\n    arrow::MemoryPool * pool,\n    gsl::span<std::uint8_t> destination)\n{\n    std::size_t const sample_count = samples.size();\n    if (sample_count > max_uncompressed_samples) {\n        return arrow::Status::Invalid(\n            sample_count, \" samples exceeds max of \", max_uncompressed_samples);\n    }\n\n    // First compress the data using svb:\n    auto const max_size = svb16_max_encoded_length(sample_count);\n    ARROW_ASSIGN_OR_RAISE(auto intermediate, arrow::AllocateResizableBuffer(max_size, pool));\n\n    static constexpr bool UseDelta = true;\n    static constexpr bool UseZigzag = true;\n    auto const encoded_count = svb16::encode<SampleType, UseDelta, UseZigzag>(\n        samples.data(), intermediate->mutable_data(), sample_count);\n    ARROW_RETURN_NOT_OK(intermediate->Resize(encoded_count));\n\n    // Now compress the svb data using zstd:\n    size_t const zstd_compressed_max_size = ZSTD_compressBound(intermediate->size());\n    if (ZSTD_isError(zstd_compressed_max_size)) {\n        return pod5::Status::Invalid(\n            \"Failed to find zstd max size for data: (\",\n            zstd_compressed_max_size,\n            \" \",\n            ZSTD_getErrorName(zstd_compressed_max_size),\n            \")\");\n    }\n\n    /* Compress.\n     * If you are doing many compressions, you may want to reuse the context.\n     * See the multiple_simple_compression.c example.\n     */\n    size_t const compressed_size = ZSTD_compress(\n        destination.data(), destination.size(), intermediate->data(), intermediate->size(), 1);\n    if (ZSTD_isError(compressed_size)) {\n        return pod5::Status::Invalid(\n            \"Failed to compress data: (\",\n            compressed_size,\n            \" \",\n            ZSTD_getErrorName(compressed_size),\n            \")\");\n    }\n    return compressed_size;\n}\n\narrow::Result<std::shared_ptr<arrow::Buffer>> compress_signal(\n    gsl::span<SampleType const> samples,\n    arrow::MemoryPool * pool)\n{\n    ARROW_ASSIGN_OR_RAISE(\n        std::size_t const sample_count, compressed_signal_max_size(samples.size()));\n\n    ARROW_ASSIGN_OR_RAISE(\n        std::shared_ptr<arrow::ResizableBuffer> out,\n        arrow::AllocateResizableBuffer(sample_count, pool));\n\n    ARROW_ASSIGN_OR_RAISE(\n        auto final_size,\n        compress_signal(samples, pool, gsl::make_span(out->mutable_data(), out->size())));\n\n    ARROW_RETURN_NOT_OK(out->Resize(final_size));\n    return out;\n}\n\narrow::Status decompress_signal(\n    gsl::span<std::uint8_t const> compressed_bytes,\n    arrow::MemoryPool * pool,\n    gsl::span<std::int16_t> destination)\n{\n    // Check that we could have compressed this size.\n    ARROW_ASSIGN_OR_RAISE(\n        std::size_t const max_compressed_size, compressed_signal_max_size(destination.size()));\n    if (compressed_bytes.size() > max_compressed_size) {\n        return pod5::Status::Invalid(\n            \"Input data corrupt: compressed input size (\",\n            compressed_bytes.size(),\n            \") exceeds max compressed output size (\",\n            max_compressed_size,\n            \")\");\n    }\n\n    // Find out how big zstd thinks the data is.\n    unsigned long long const decompressed_zstd_size =\n        ZSTD_getFrameContentSize(compressed_bytes.data(), compressed_bytes.size());\n    if (ZSTD_isError(decompressed_zstd_size)) {\n        return pod5::Status::Invalid(\n            \"Input data not compressed by zstd: (\",\n            decompressed_zstd_size,\n            \" \",\n            ZSTD_getErrorName(decompressed_zstd_size),\n            \")\");\n    }\n\n    // Documentation of |ZSTD_getFrameContentSize| explicitly states that we should bounds check this:\n    //     *   note 5 : If source is untrusted, decompressed size could be wrong or intentionally modified.\n    //     *            Always ensure return value fits within application's authorized limits.\n    //     *            Each application can set its own limits.\n    std::size_t const max_svb16_compressed_size = svb16_max_encoded_length(destination.size());\n    if (decompressed_zstd_size > max_svb16_compressed_size) {\n        return arrow::Status::Invalid(\n            \"Input data corrupt: claimed size (\",\n            decompressed_zstd_size,\n            \") exceeds max compressed output size (\",\n            max_svb16_compressed_size,\n            \")\");\n    }\n\n    // Check that we have enough memory to decompress.\n    // Note: this will return 0 on unsupported platforms, so we skip it there.\n    std::int64_t const system_memory = arrow::internal::GetTotalMemoryBytes();\n    assert(system_memory > 0);\n    if (system_memory > 0 && decompressed_zstd_size >= static_cast<std::size_t>(system_memory)) {\n        return arrow::Status::OutOfMemory(\n            \"Not enough system memory (\",\n            system_memory,\n            \") to decompress file (\",\n            decompressed_zstd_size,\n            \")\");\n    }\n\n    if (POD5_ENABLE_FUZZERS && decompressed_zstd_size > 1'000'000) {\n        return arrow::Status::Invalid(\"Skipping huge sizes when fuzzing\");\n    }\n\n    thread_local DecompressContext decompress_context;\n    if (!decompress_context) {\n        return arrow::Status::OutOfMemory(\"Failed to create zstd decompress context\");\n    }\n\n    // Decompress the data using zstd.\n    auto const allocation_padding = svb16::decode_input_buffer_padding_byte_count();\n    ARROW_ASSIGN_OR_RAISE(\n        auto intermediate,\n        arrow::AllocateResizableBuffer(decompressed_zstd_size + allocation_padding, pool));\n    size_t const decompress_res = ZSTD_decompressDCtx(\n        decompress_context.get(),\n        intermediate->mutable_data(),\n        intermediate->size(),\n        compressed_bytes.data(),\n        compressed_bytes.size());\n    if (ZSTD_isError(decompress_res)) {\n        return pod5::Status::Invalid(\n            \"Input data failed to decompress using zstd: (\",\n            decompress_res,\n            \" \",\n            ZSTD_getErrorName(decompress_res),\n            \")\");\n    }\n\n    auto const svb16_compressed_data_with_padding =\n        gsl::make_span(intermediate->data(), intermediate->size());\n    auto const svb16_compressed_data_no_padding =\n        svb16_compressed_data_with_padding.subspan(0, decompressed_zstd_size);\n\n    // Validate the data.\n    if (!svb16::validate(svb16_compressed_data_no_padding, destination.size())) {\n        return pod5::Status::Invalid(\"Compressed signal data is corrupt\");\n    }\n\n    // Now decompress the data using svb:\n    static constexpr bool UseDelta = true;\n    static constexpr bool UseZigzag = true;\n    auto consumed_count = svb16::decode<SampleType, UseDelta, UseZigzag>(\n        destination, svb16_compressed_data_with_padding);\n    if (consumed_count != decompressed_zstd_size) {\n        return pod5::Status::Invalid(\"Remaining data at end of signal buffer\");\n    }\n\n    return pod5::Status::OK();\n}\n\narrow::Result<std::shared_ptr<arrow::Buffer>> decompress_signal(\n    gsl::span<std::uint8_t const> compressed_bytes,\n    std::uint32_t samples_count,\n    arrow::MemoryPool * pool)\n{\n    ARROW_ASSIGN_OR_RAISE(\n        std::shared_ptr<arrow::ResizableBuffer> out,\n        arrow::AllocateResizableBuffer(samples_count * sizeof(SampleType), pool));\n\n    auto signal_span = gsl::make_span(out->mutable_data(), out->size()).as_span<std::int16_t>();\n\n    ARROW_RETURN_NOT_OK(decompress_signal(compressed_bytes, pool, signal_span));\n    return out;\n}\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/signal_compression.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/result.h\"\n\n#include <gsl/gsl-lite.hpp>\n\nnamespace arrow {\nclass MemoryPool;\nclass Buffer;\n}  // namespace arrow\n\nnamespace pod5 {\n\nusing SampleType = std::int16_t;\n\nPOD5_FORMAT_EXPORT arrow::Result<std::size_t> compressed_signal_max_size(std::size_t sample_count);\n\nPOD5_FORMAT_EXPORT arrow::Result<std::size_t> compress_signal(\n    gsl::span<SampleType const> samples,\n    arrow::MemoryPool * pool,\n    gsl::span<std::uint8_t> destination);\n\nPOD5_FORMAT_EXPORT arrow::Result<std::shared_ptr<arrow::Buffer>> compress_signal(\n    gsl::span<SampleType const> samples,\n    arrow::MemoryPool * pool);\n\nPOD5_FORMAT_EXPORT arrow::Result<std::shared_ptr<arrow::Buffer>> decompress_signal(\n    gsl::span<std::uint8_t const> compressed_bytes,\n    std::uint32_t samples_count,\n    arrow::MemoryPool * pool);\n\nPOD5_FORMAT_EXPORT arrow::Status decompress_signal(\n    gsl::span<std::uint8_t const> compressed_bytes,\n    arrow::MemoryPool * pool,\n    gsl::span<std::int16_t> destination);\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/signal_table_reader.cpp",
    "content": "#include \"pod5_format/signal_table_reader.h\"\n\n#include \"pod5_format/schema_metadata.h\"\n#include \"pod5_format/signal_compression.h\"\n#include \"pod5_format/table_reader.h\"\n\n#include <arrow/array/array_nested.h>\n#include <arrow/array/array_primitive.h>\n#include <arrow/ipc/reader.h>\n\n#include <iostream>\n\nnamespace pod5 {\n\nstruct SignalTableReaderCacheCleaner {\n    static void make_space_in_table_batches(\n        std::unordered_map<std::size_t, SignalTableReader::CachedItem> & cached_batches)\n    {\n        std::vector<std::pair<std::size_t, SignalTableReader::AccessIndex>> access_ordered_data;\n        access_ordered_data.reserve(cached_batches.size());\n\n        for (auto item : cached_batches) {\n            access_ordered_data.emplace_back(\n                std::make_pair(item.first, item.second.last_access_index));\n        }\n        std::sort(\n            access_ordered_data.begin(),\n            access_ordered_data.end(),\n            [](auto const & a, auto const & b) { return a.second < b.second; });\n\n        // Clear about 20% of the cache to make space for further growth:\n        auto const to_clear = std::max<std::size_t>(1, cached_batches.size() * 0.2f);\n        for (std::size_t i = 0; i < to_clear; ++i) {\n            auto const index_to_remove = access_ordered_data[i].first;\n            cached_batches.erase(index_to_remove);\n        }\n    }\n};\n\nSignalTableRecordBatch::SignalTableRecordBatch(\n    std::shared_ptr<arrow::RecordBatch> const & batch,\n    SignalTableSchemaDescription field_locations,\n    arrow::MemoryPool * pool)\n: TableRecordBatch(batch)\n, m_field_locations(field_locations)\n, m_pool(pool)\n{\n}\n\nstd::shared_ptr<UuidArray> SignalTableRecordBatch::read_id_column() const\n{\n    return std::static_pointer_cast<UuidArray>(batch()->column(m_field_locations.read_id));\n}\n\nstd::shared_ptr<arrow::LargeListArray> SignalTableRecordBatch::uncompressed_signal_column() const\n{\n    return std::static_pointer_cast<arrow::LargeListArray>(\n        batch()->column(m_field_locations.signal));\n}\n\nstd::shared_ptr<VbzSignalArray> SignalTableRecordBatch::vbz_signal_column() const\n{\n    return std::static_pointer_cast<VbzSignalArray>(batch()->column(m_field_locations.signal));\n}\n\nstd::shared_ptr<arrow::UInt32Array> SignalTableRecordBatch::samples_column() const\n{\n    return std::static_pointer_cast<arrow::UInt32Array>(batch()->column(m_field_locations.samples));\n}\n\nResult<std::size_t> SignalTableRecordBatch::samples_byte_count(std::size_t row_index) const\n{\n    switch (m_field_locations.signal_type) {\n    case SignalType::UncompressedSignal: {\n        auto signal_column = uncompressed_signal_column();\n        auto signal = signal_column->value_slice(row_index);\n        return signal->length() * sizeof(std::int16_t);\n    }\n    case SignalType::VbzSignal: {\n        auto signal_column = vbz_signal_column();\n        auto signal_compressed = signal_column->Value(row_index);\n        return signal_compressed.size();\n    }\n    }\n\n    return pod5::Status::Invalid(\"Unknown signal type\");\n}\n\nStatus SignalTableRecordBatch::extract_signal_row(\n    std::size_t row_index,\n    gsl::span<std::int16_t> samples) const\n{\n    if (row_index >= num_rows()) {\n        return pod5::Status::Invalid(\n            \"Queried signal row \",\n            row_index,\n            \" is outside the available rows (\",\n            num_rows(),\n            \" in batch)\");\n    }\n\n    auto sample_count = samples_column();\n    auto samples_in_row = sample_count->Value(row_index);\n    if (samples_in_row != samples.size()) {\n        return pod5::Status::Invalid(\n            \"Unexpected size for sample array \", samples.size(), \" expected \", samples_in_row);\n    }\n\n    switch (m_field_locations.signal_type) {\n    case SignalType::UncompressedSignal: {\n        auto signal_column = uncompressed_signal_column();\n        auto signal =\n            std::static_pointer_cast<arrow::Int16Array>(signal_column->value_slice(row_index));\n        std::copy(signal->raw_values(), signal->raw_values() + signal->length(), samples.begin());\n        return Status::OK();\n    }\n    case SignalType::VbzSignal: {\n        auto signal_column = vbz_signal_column();\n        auto signal_compressed = signal_column->Value(row_index);\n        return pod5::decompress_signal(signal_compressed, m_pool, samples);\n    }\n    }\n\n    return pod5::Status::Invalid(\"Unknown signal type\");\n}\n\nResult<std::shared_ptr<arrow::Buffer>> SignalTableRecordBatch::extract_signal_row_inplace(\n    std::size_t row_index) const\n{\n    if (row_index >= num_rows()) {\n        return pod5::Status::Invalid(\n            \"Queried signal row \",\n            row_index,\n            \" is outside the available rows (\",\n            num_rows(),\n            \" in batch)\");\n    }\n\n    switch (m_field_locations.signal_type) {\n    case SignalType::UncompressedSignal: {\n        auto signal_column = uncompressed_signal_column();\n        auto const value_slice =\n            std::static_pointer_cast<arrow::Int16Array>(signal_column->value_slice(row_index));\n\n        auto const element_size =\n            sizeof(std::remove_reference<decltype(*signal_column)>::type::TypeClass);\n\n        auto const values = value_slice->values();\n        auto offset = signal_column->value_offset(row_index);\n        auto length = signal_column->value_length(row_index);\n        return arrow::SliceBuffer(values, offset * element_size, length * element_size);\n    }\n    case SignalType::VbzSignal: {\n        auto signal_column = vbz_signal_column();\n        return signal_column->ValueAsBuffer(row_index);\n    }\n    }\n\n    return pod5::Status::Invalid(\"Unknown signal type\");\n}\n\n//---------------------------------------------------------------------------------------------------------------------\n\nSignalTableReader::SignalTableReader(\n    std::shared_ptr<void> && input_source,\n    std::shared_ptr<arrow::ipc::RecordBatchFileReader> && reader,\n    SignalTableSchemaDescription field_locations,\n    SchemaMetadataDescription && schema_metadata,\n    std::size_t num_record_batches,\n    std::size_t batch_size,\n    std::size_t max_cached_table_batches,\n    arrow::MemoryPool * pool)\n: TableReader(std::move(input_source), std::move(reader), std::move(schema_metadata), pool)\n, m_field_locations(field_locations)\n, m_pool(pool)\n, m_max_cached_table_batches(max_cached_table_batches)\n, m_table_batches(num_record_batches)\n, m_batch_size(batch_size)\n{\n}\n\nSignalTableReader::SignalTableReader(SignalTableReader && other)\n: TableReader(std::move(other))\n, m_field_locations(std::move(other.m_field_locations))\n, m_pool(other.m_pool)\n, m_max_cached_table_batches(other.m_max_cached_table_batches)\n, m_table_batches(std::move(other.m_table_batches))\n, m_batch_size(other.m_batch_size)\n{\n}\n\nSignalTableReader & SignalTableReader::operator=(SignalTableReader && other)\n{\n    m_field_locations = std::move(other.m_field_locations);\n    m_pool = other.m_pool;\n    m_max_cached_table_batches = other.m_max_cached_table_batches;\n    m_batch_size = other.m_batch_size;\n    m_table_batches = std::move(other.m_table_batches);\n    static_cast<TableReader &>(*this) = std::move(static_cast<TableReader &>(other));\n    return *this;\n}\n\nResult<SignalTableRecordBatch> SignalTableReader::read_record_batch(std::size_t i) const\n{\n    std::lock_guard<std::mutex> l(m_batch_get_mutex);\n    if (m_last_read_record_batch_index == i) {\n        return pod5::SignalTableRecordBatch{m_last_read_record_batch, m_field_locations, m_pool};\n    }\n\n    auto it = m_table_batches.find(i);\n    if (it != m_table_batches.end()) {\n        it->second.last_access_index = m_last_access_index++;\n        return it->second.item;\n    }\n\n    // If limited in cached batches, then ensure we apply limit:\n    if (m_max_cached_table_batches != 0 && m_table_batches.size() >= m_max_cached_table_batches) {\n        SignalTableReaderCacheCleaner::make_space_in_table_batches(m_table_batches);\n        assert(m_table_batches.size() < m_max_cached_table_batches);\n    }\n\n    ARROW_ASSIGN_OR_RAISE(m_last_read_record_batch, TableReader::ReadRecordBatch(i));\n    m_last_read_record_batch_index = i;\n    auto inserted = m_table_batches.emplace(\n        i,\n        CachedItem{\n            pod5::SignalTableRecordBatch{m_last_read_record_batch, m_field_locations, m_pool},\n            m_last_access_index++});\n    return inserted.first->second.item;\n}\n\nResult<std::size_t> SignalTableReader::signal_batch_for_row_id(\n    std::uint64_t row,\n    std::size_t * batch_row) const\n{\n    if (m_batch_size == 0) {\n        return Status::Invalid(\"Invalid row '\", row, \"' for file with zero signal rows.\");\n    }\n\n    auto batch = row / m_batch_size;\n\n    if (batch_row) {\n        *batch_row = row - (batch * m_batch_size);\n    }\n\n    if (batch >= num_record_batches()) {\n        return Status::Invalid(\"Row outside batch bounds\");\n    }\n\n    return batch;\n}\n\nResult<std::size_t> SignalTableReader::extract_sample_count(\n    gsl::span<std::uint64_t const> const & row_indices) const\n{\n    std::size_t sample_count = 0;\n    for (auto const & signal_row : row_indices) {\n        std::size_t batch_row = 0;\n        ARROW_ASSIGN_OR_RAISE(\n            auto const signal_batch_index, signal_batch_for_row_id(signal_row, &batch_row));\n\n        ARROW_ASSIGN_OR_RAISE(auto const & signal_batch, read_record_batch(signal_batch_index));\n        auto const & samples_column = signal_batch.samples_column();\n        sample_count += samples_column->Value(batch_row);\n    }\n    return sample_count;\n}\n\nStatus SignalTableReader::extract_samples(\n    gsl::span<std::uint64_t const> const & row_indices,\n    gsl::span<std::int16_t> const & output_samples) const\n{\n    std::size_t sample_count = 0;\n\n    for (auto const & signal_row : row_indices) {\n        std::size_t batch_row = 0;\n        ARROW_ASSIGN_OR_RAISE(\n            auto const signal_batch_index, signal_batch_for_row_id(signal_row, &batch_row));\n\n        ARROW_ASSIGN_OR_RAISE(auto const & signal_batch, read_record_batch(signal_batch_index));\n        auto const & samples_column = signal_batch.samples_column();\n        auto const row_samples_count = samples_column->Value(batch_row);\n        std::size_t const sample_start = sample_count;\n        sample_count += row_samples_count;\n        if (sample_count > output_samples.size()) {\n            return Status::Invalid(\"Too few samples in input samples array\");\n        }\n\n        ARROW_RETURN_NOT_OK(signal_batch.extract_signal_row(\n            batch_row, output_samples.subspan(sample_start, row_samples_count)));\n    }\n    return Status::OK();\n}\n\nResult<std::vector<std::shared_ptr<arrow::Buffer>>> SignalTableReader::extract_samples_inplace(\n    gsl::span<std::uint64_t const> const & row_indices,\n    std::vector<std::uint32_t> & sample_count) const\n{\n    std::vector<std::shared_ptr<arrow::Buffer>> sample_buffers;\n\n    for (auto const & signal_row : row_indices) {\n        std::size_t batch_row = 0;\n        ARROW_ASSIGN_OR_RAISE(\n            auto const signal_batch_index, signal_batch_for_row_id(signal_row, &batch_row));\n\n        ARROW_ASSIGN_OR_RAISE(auto const & signal_batch, read_record_batch(signal_batch_index));\n\n        ARROW_ASSIGN_OR_RAISE(auto signal_data, signal_batch.extract_signal_row_inplace(batch_row));\n        sample_buffers.emplace_back(std::move(signal_data));\n\n        auto const & samples_column = signal_batch.samples_column();\n        sample_count.push_back(samples_column->Value(batch_row));\n    }\n    return sample_buffers;\n}\n\nSignalType SignalTableReader::signal_type() const { return m_field_locations.signal_type; }\n\n//---------------------------------------------------------------------------------------------------------------------\nResult<SignalTableReader> make_signal_table_reader(\n    std::shared_ptr<arrow::io::RandomAccessFile> const & input,\n    std::size_t max_cached_table_batches,\n    arrow::MemoryPool * pool)\n{\n    arrow::ipc::IpcReadOptions options;\n    options.memory_pool = pool;\n\n    ARROW_ASSIGN_OR_RAISE(auto reader, arrow::ipc::RecordBatchFileReader::Open(input, options));\n\n    auto read_metadata_key_values = reader->schema()->metadata();\n    if (!read_metadata_key_values) {\n        return Status::IOError(\"Missing metadata on signal table schema\");\n    }\n    ARROW_ASSIGN_OR_RAISE(\n        auto read_metadata, read_schema_key_value_metadata(read_metadata_key_values));\n    ARROW_ASSIGN_OR_RAISE(auto field_locations, read_signal_table_schema(reader->schema()));\n\n    std::size_t const num_record_batches = reader->num_record_batches();\n    std::size_t batch_size = 0;\n    if (num_record_batches > 0) {\n        ARROW_ASSIGN_OR_RAISE(auto const batch_zero, ReadRecordBatchAndValidate(*reader, 0));\n        batch_size = batch_zero->num_rows();\n    }\n\n    return SignalTableReader(\n        {input},\n        std::move(reader),\n        field_locations,\n        std::move(read_metadata),\n        num_record_batches,\n        batch_size,\n        max_cached_table_batches,\n        pool);\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/signal_table_reader.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/schema_metadata.h\"\n#include \"pod5_format/signal_table_schema.h\"\n#include \"pod5_format/table_reader.h\"\n#include \"pod5_format/types.h\"\n\n#include <arrow/io/type_fwd.h>\n#include <gsl/gsl-lite.hpp>\n\n#include <atomic>\n#include <mutex>\n#include <unordered_map>\n\nnamespace arrow {\nclass Schema;\n\nnamespace io {\nclass RandomAccessFile;\n}\n\nnamespace ipc {\nclass RecordBatchFileReader;\n}\n}  // namespace arrow\n\nnamespace pod5 {\n\nstruct SignalTableReaderCacheCleaner;\n\nclass POD5_FORMAT_EXPORT SignalTableRecordBatch : public TableRecordBatch {\npublic:\n    SignalTableRecordBatch(\n        std::shared_ptr<arrow::RecordBatch> const & batch,\n        SignalTableSchemaDescription field_locations,\n        arrow::MemoryPool * pool);\n\n    std::shared_ptr<UuidArray> read_id_column() const;\n    std::shared_ptr<arrow::LargeListArray> uncompressed_signal_column() const;\n    std::shared_ptr<VbzSignalArray> vbz_signal_column() const;\n    std::shared_ptr<arrow::UInt32Array> samples_column() const;\n\n    Result<std::size_t> samples_byte_count(std::size_t row_index) const;\n\n    /// \\brief Extract a row of sample data into [samples], decompressing if required.\n    Status extract_signal_row(std::size_t row_index, gsl::span<std::int16_t> samples) const;\n    Result<std::shared_ptr<arrow::Buffer>> extract_signal_row_inplace(std::size_t row_index) const;\n\nprivate:\n    SignalTableSchemaDescription m_field_locations;\n    arrow::MemoryPool * m_pool;\n};\n\nclass POD5_FORMAT_EXPORT SignalTableReader : public TableReader {\npublic:\n    SignalTableReader(\n        std::shared_ptr<void> && input_source,\n        std::shared_ptr<arrow::ipc::RecordBatchFileReader> && reader,\n        SignalTableSchemaDescription field_locations,\n        SchemaMetadataDescription && schema_metadata,\n        std::size_t num_record_batches,\n        std::size_t batch_size,\n        std::size_t max_cached_table_batches,\n        arrow::MemoryPool * pool);\n\n    SignalTableReader(SignalTableReader &&);\n    SignalTableReader & operator=(SignalTableReader &&);\n\n    Result<SignalTableRecordBatch> read_record_batch(std::size_t i) const;\n\n    Result<std::size_t> signal_batch_for_row_id(std::uint64_t row, std::size_t * batch_row) const;\n\n    /// \\brief Find the number of samples in a given list of rows.\n    /// \\param row_indices      The rows to query for sample ount.\n    /// \\returns The sum of all sample counts on input rows.\n    Result<std::size_t> extract_sample_count(\n        gsl::span<std::uint64_t const> const & row_indices) const;\n\n    /// \\brief Extract the samples for a list of rows.\n    /// \\param row_indices      The rows to query for samples.\n    /// \\param output_samples   The output samples from the rows. Data in the vector is cleared before appending.\n    Status extract_samples(\n        gsl::span<std::uint64_t const> const & row_indices,\n        gsl::span<std::int16_t> const & output_samples) const;\n\n    /// \\brief Extract the samples as written in the arrow table for a list of rows.\n    /// \\param row_indices      The rows to query for samples.\n    Result<std::vector<std::shared_ptr<arrow::Buffer>>> extract_samples_inplace(\n        gsl::span<std::uint64_t const> const & row_indices,\n        std::vector<std::uint32_t> & sample_count) const;\n\n    /// \\brief Find the signal type of this writer\n    SignalType signal_type() const;\n\nprivate:\n    SignalTableSchemaDescription m_field_locations;\n    arrow::MemoryPool * m_pool;\n    std::size_t m_max_cached_table_batches;\n\n    mutable std::size_t m_last_read_record_batch_index = -1;\n    mutable std::shared_ptr<arrow::RecordBatch> m_last_read_record_batch;\n\n    mutable std::mutex m_batch_get_mutex;\n    using AccessIndex = std::uint64_t;\n\n    struct CachedItem {\n        pod5::SignalTableRecordBatch item;\n        AccessIndex last_access_index;\n    };\n\n    mutable std::unordered_map<std::size_t, CachedItem> m_table_batches;\n\n    mutable AccessIndex m_last_access_index = 0;\n\n    std::size_t m_batch_size;\n\n    friend struct SignalTableReaderCacheCleaner;\n};\n\nPOD5_FORMAT_EXPORT Result<SignalTableReader> make_signal_table_reader(\n    std::shared_ptr<arrow::io::RandomAccessFile> const & sink,\n    std::size_t max_cached_table_batches,\n    arrow::MemoryPool * pool);\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/signal_table_schema.cpp",
    "content": "#include \"pod5_format/signal_table_schema.h\"\n\n#include \"pod5_format/schema_utils.h\"\n#include \"pod5_format/types.h\"\n\n#include <arrow/type.h>\n\nnamespace pod5 {\n\nstd::shared_ptr<arrow::Schema> make_signal_table_schema(\n    SignalType signal_type,\n    std::shared_ptr<arrow::KeyValueMetadata const> const & metadata,\n    SignalTableSchemaDescription * field_locations)\n{\n    auto const uuid_type = uuid();\n\n    if (field_locations) {\n        *field_locations = {};\n        field_locations->signal_type = signal_type;\n    }\n\n    std::shared_ptr<arrow::DataType> signal_schema_type;\n    switch (signal_type) {\n    case SignalType::UncompressedSignal:\n        signal_schema_type = arrow::large_list(arrow::int16());\n        break;\n    case SignalType::VbzSignal:\n        signal_schema_type = vbz_signal();\n        break;\n    }\n\n    return arrow::schema(\n        {\n            arrow::field(\"read_id\", uuid_type),\n            arrow::field(\"signal\", signal_schema_type),\n            arrow::field(\"samples\", arrow::uint32()),\n        },\n        metadata);\n}\n\nResult<SignalTableSchemaDescription> read_signal_table_schema(\n    std::shared_ptr<arrow::Schema> const & schema)\n{\n    ARROW_ASSIGN_OR_RAISE(auto read_id_field_idx, find_field(schema, \"read_id\", uuid()));\n    ARROW_ASSIGN_OR_RAISE(auto samples_field_idx, find_field(schema, \"samples\", arrow::uint32()));\n\n    ARROW_ASSIGN_OR_RAISE(auto signal_field_idx, find_field_untyped(schema, \"signal\"));\n    SignalType signal_type = SignalType::UncompressedSignal;\n    {\n        auto const signal_field = schema->field(signal_field_idx);\n\n        auto const signal_arrow_type = signal_field->type();\n        if (signal_arrow_type->id() == arrow::Type::LARGE_LIST) {\n            auto const & signal_list_field =\n                static_cast<arrow::LargeListType const &>(*signal_arrow_type);\n            if (signal_list_field.value_type()->id() != arrow::Type::INT16) {\n                return Status::TypeError(\"Schema field 'signal' list value type is incorrect type\");\n            }\n        } else if (signal_arrow_type->Equals(vbz_signal())) {\n            signal_type = SignalType::VbzSignal;\n        } else {\n            return Status::TypeError(\n                \"Schema field 'signal' is incorrect type: '\", signal_arrow_type->name(), \"'\");\n        }\n    }\n\n    return SignalTableSchemaDescription{\n        signal_type, read_id_field_idx, signal_field_idx, samples_field_idx};\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/signal_table_schema.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/signal_table_utils.h\"\n\n#include <memory>\n\nnamespace arrow {\nclass KeyValueMetadata;\nclass Schema;\n}  // namespace arrow\n\nnamespace pod5 {\n\nstruct SignalTableSchemaDescription {\n    SignalType signal_type;\n\n    int read_id = 0;\n    int signal = 1;\n    int samples = 2;\n};\n\n/// \\brief Make a new schema for a signal table.\n/// \\param signal_type The type of signal to use.\n/// \\param metadata Metadata to be applied to the schema.\n/// \\param field_locations [optional] The signal table field locations, for use when writing to the table.\n/// \\returns The schema for a signal table.\nPOD5_FORMAT_EXPORT std::shared_ptr<arrow::Schema> make_signal_table_schema(\n    SignalType signal_type,\n    std::shared_ptr<arrow::KeyValueMetadata const> const & metadata,\n    SignalTableSchemaDescription * field_locations);\n\nPOD5_FORMAT_EXPORT Result<SignalTableSchemaDescription> read_signal_table_schema(\n    std::shared_ptr<arrow::Schema> const &);\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/signal_table_utils.h",
    "content": "#pragma once\n\nnamespace pod5 {\n\nusing SignalTableRowIndex = std::uint64_t;\n\nenum class SignalType {\n    UncompressedSignal,\n    VbzSignal,\n};\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/signal_table_writer.cpp",
    "content": "#include \"pod5_format/signal_table_writer.h\"\n\n#include \"pod5_format/file_output_stream.h\"\n#include \"pod5_format/internal/tracing/tracing.h\"\n#include \"pod5_format/types.h\"\n\n#include <arrow/array/builder_binary.h>\n#include <arrow/array/builder_nested.h>\n#include <arrow/array/builder_primitive.h>\n#include <arrow/array/util.h>\n#include <arrow/extension_type.h>\n#include <arrow/ipc/writer.h>\n#include <arrow/record_batch.h>\n#include <arrow/type.h>\n\nnamespace pod5 {\n\nSignalTableWriter::SignalTableWriter(\n    std::shared_ptr<arrow::ipc::RecordBatchWriter> && writer,\n    std::shared_ptr<arrow::Schema> && schema,\n    SignalBuilderVariant && signal_builder,\n    SignalTableSchemaDescription const & field_locations,\n    std::shared_ptr<FileOutputStream> const & output_stream,\n    std::size_t table_batch_size,\n    arrow::MemoryPool * pool)\n: m_pool(pool)\n, m_schema(schema)\n, m_field_locations(field_locations)\n, m_output_stream{output_stream}\n, m_table_batch_size(table_batch_size)\n, m_writer(std::move(writer))\n, m_signal_builder(std::move(signal_builder))\n{\n    m_read_id_builder = make_read_id_builder(m_pool);\n    m_samples_builder = std::make_unique<arrow::UInt32Builder>(m_pool);\n}\n\nSignalTableWriter::SignalTableWriter(SignalTableWriter && other) = default;\nSignalTableWriter & SignalTableWriter::operator=(SignalTableWriter &&) = default;\n\nSignalTableWriter::~SignalTableWriter()\n{\n    if (m_writer) {\n        (void)close();\n    }\n}\n\nResult<SignalTableRowIndex> SignalTableWriter::add_signal(\n    Uuid const & read_id,\n    gsl::span<std::int16_t const> const & signal)\n{\n    POD5_TRACE_FUNCTION();\n    if (!m_writer) {\n        return Status::IOError(\"Writer terminated\");\n    }\n\n    ARROW_RETURN_NOT_OK(reserve_rows());\n\n    auto row_id = m_written_batched_row_count + m_current_batch_row_count;\n    ARROW_RETURN_NOT_OK(m_read_id_builder->Append(read_id.data()));\n\n    ARROW_RETURN_NOT_OK(std::visit(visitors::append_signal{signal, m_pool}, m_signal_builder));\n\n    ARROW_RETURN_NOT_OK(m_samples_builder->Append(signal.size()));\n    ++m_current_batch_row_count;\n\n    if (m_current_batch_row_count >= m_table_batch_size) {\n        ARROW_RETURN_NOT_OK(write_batch());\n    }\n\n    return row_id;\n}\n\nResult<SignalTableRowIndex> SignalTableWriter::add_pre_compressed_signal(\n    Uuid const & read_id,\n    gsl::span<std::uint8_t const> const & signal,\n    std::uint32_t sample_count)\n{\n    POD5_TRACE_FUNCTION();\n    if (!m_writer) {\n        return Status::IOError(\"Writer terminated\");\n    }\n\n    ARROW_RETURN_NOT_OK(reserve_rows());\n\n    auto row_id = m_written_batched_row_count + m_current_batch_row_count;\n    ARROW_RETURN_NOT_OK(m_read_id_builder->Append(read_id.data()));\n\n    ARROW_RETURN_NOT_OK(\n        std::visit(visitors::append_pre_compressed_signal{signal}, m_signal_builder));\n\n    ARROW_RETURN_NOT_OK(m_samples_builder->Append(sample_count));\n    ++m_current_batch_row_count;\n\n    if (m_current_batch_row_count >= m_table_batch_size) {\n        ARROW_RETURN_NOT_OK(write_batch());\n    }\n\n    return row_id;\n}\n\npod5::Result<std::pair<SignalTableRowIndex, SignalTableRowIndex>>\nSignalTableWriter::add_signal_batch(\n    std::size_t row_count,\n    std::vector<std::shared_ptr<arrow::Array>> && columns,\n    bool final_batch)\n{\n    POD5_TRACE_FUNCTION();\n    if (!m_writer) {\n        return Status::Invalid(\"Unable to write batches, writer is closed.\");\n    }\n\n    if (m_current_batch_row_count != 0) {\n        return Status::Invalid(\"Unable to write batches directly and using per read methods\");\n    }\n\n    if (!final_batch && row_count != m_table_batch_size) {\n        return Status::Invalid(\"Unable to write invalid sized signal batch to signal table\");\n    }\n\n    auto const record_batch = arrow::RecordBatch::Make(m_schema, row_count, std::move(columns));\n    ARROW_RETURN_NOT_OK(m_writer->WriteRecordBatch(*record_batch));\n    if (final_batch) {\n        ARROW_RETURN_NOT_OK(close());\n    }\n\n    auto first_row_id = m_written_batched_row_count;\n    m_written_batched_row_count += row_count;\n    return std::make_pair(first_row_id, m_written_batched_row_count);\n}\n\nStatus SignalTableWriter::close()\n{\n    // Check for already closed\n    if (!m_writer) {\n        return Status::OK();\n    }\n\n    ARROW_RETURN_NOT_OK(write_batch());\n\n    ARROW_RETURN_NOT_OK(m_writer->Close());\n    m_writer = nullptr;\n    return Status::OK();\n}\n\nSignalType SignalTableWriter::signal_type() const { return m_field_locations.signal_type; }\n\nStatus SignalTableWriter::write_batch(arrow::RecordBatch const & record_batch)\n{\n    ARROW_RETURN_NOT_OK(m_writer->WriteRecordBatch(record_batch));\n    return m_output_stream->batch_complete();\n}\n\nStatus SignalTableWriter::write_batch()\n{\n    POD5_TRACE_FUNCTION();\n    if (m_current_batch_row_count == 0) {\n        return Status::OK();\n    }\n\n    if (!m_writer) {\n        return Status::IOError(\"Writer terminated\");\n    }\n\n    std::vector<std::shared_ptr<arrow::Array>> columns{nullptr, nullptr, nullptr};\n    ARROW_RETURN_NOT_OK(m_read_id_builder->Finish(&columns[m_field_locations.read_id]));\n\n    ARROW_RETURN_NOT_OK(\n        std::visit(visitors::finish_column{&columns[m_field_locations.signal]}, m_signal_builder));\n\n    ARROW_RETURN_NOT_OK(m_samples_builder->Finish(&columns[m_field_locations.samples]));\n\n    auto const record_batch =\n        arrow::RecordBatch::Make(m_schema, m_current_batch_row_count, std::move(columns));\n    m_written_batched_row_count += m_current_batch_row_count;\n    m_current_batch_row_count = 0;\n\n    ARROW_RETURN_NOT_OK(m_writer->WriteRecordBatch(*record_batch));\n    return m_output_stream->batch_complete();\n}\n\nStatus SignalTableWriter::reserve_rows()\n{\n    // Only reserve if we have not already reserved (at the start of a batch)\n    if (m_current_batch_row_count > 0) {\n        return arrow::Status::OK();\n    }\n\n    ARROW_RETURN_NOT_OK(m_read_id_builder->Reserve(m_table_batch_size));\n    ARROW_RETURN_NOT_OK(m_samples_builder->Reserve(m_table_batch_size));\n\n    static constexpr std::uint32_t APPROX_READ_SIZE = 102'400;\n\n    return std::visit(\n        visitors::reserve_rows{m_table_batch_size, APPROX_READ_SIZE}, m_signal_builder);\n}\n\nResult<SignalTableWriter> make_signal_table_writer(\n    std::shared_ptr<FileOutputStream> const & sink,\n    std::shared_ptr<arrow::KeyValueMetadata const> const & metadata,\n    std::size_t table_batch_size,\n    SignalType compression_type,\n    arrow::MemoryPool * pool)\n{\n    SignalTableSchemaDescription field_locations;\n    auto schema = make_signal_table_schema(compression_type, metadata, &field_locations);\n\n    arrow::ipc::IpcWriteOptions options;\n    options.memory_pool = pool;\n\n    ARROW_ASSIGN_OR_RAISE(auto writer, arrow::ipc::MakeFileWriter(sink, schema, options, metadata));\n\n    ARROW_ASSIGN_OR_RAISE(auto signal_builder, make_signal_builder(compression_type, pool));\n\n    auto signal_table_writer = SignalTableWriter(\n        std::move(writer),\n        std::move(schema),\n        std::move(signal_builder),\n        field_locations,\n        sink,\n        table_batch_size,\n        pool);\n\n    return signal_table_writer;\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/signal_table_writer.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/signal_builder.h\"\n#include \"pod5_format/signal_table_schema.h\"\n#include \"pod5_format/uuid.h\"\n\n#include <arrow/io/type_fwd.h>\n#include <gsl/gsl-lite.hpp>\n\nnamespace arrow {\nclass Schema;\n\nnamespace ipc {\nclass RecordBatchWriter;\n}\n}  // namespace arrow\n\nnamespace pod5 {\n\nclass FileOutputStream;\n\nclass POD5_FORMAT_EXPORT SignalTableWriter {\npublic:\n    SignalTableWriter(\n        std::shared_ptr<arrow::ipc::RecordBatchWriter> && writer,\n        std::shared_ptr<arrow::Schema> && schema,\n        SignalBuilderVariant && signal_builder,\n        SignalTableSchemaDescription const & field_locations,\n        std::shared_ptr<FileOutputStream> const & output_stream,\n        std::size_t table_batch_size,\n        arrow::MemoryPool * pool);\n    SignalTableWriter(SignalTableWriter &&);\n    SignalTableWriter & operator=(SignalTableWriter &&);\n    SignalTableWriter(SignalTableWriter const &) = delete;\n    SignalTableWriter & operator=(SignalTableWriter const &) = delete;\n    ~SignalTableWriter();\n\n    /// \\brief Find the size of table batches for the signal table writer.\n    std::size_t table_batch_size() const { return m_table_batch_size; }\n\n    /// \\brief Add a read to the signal table, adding to the current batch.\n    /// \\param read_id The read id for the read entry\n    /// \\param signal The signal for the read entry\n    /// \\returns The row index of the inserted signal, or a status on failure.\n    Result<SignalTableRowIndex> add_signal(\n        Uuid const & read_id,\n        gsl::span<std::int16_t const> const & signal);\n\n    /// \\brief Add a pre-compressed read to the signal table, adding to the current batch.\n    ///        The batch is not flushed to disk until #flush is called.\n    ///\n    ///        The user should call #compress_signal on *this* writer to compress the signal prior\n    ///        to calling this method, to ensure the signal is compressed correctly for the table.\n    ///\n    /// \\param read_id The read id for the read entry\n    /// \\param signal The signal for the read entry\n    /// \\returns The row index of the inserted signal, or a status on failure.\n    Result<SignalTableRowIndex> add_pre_compressed_signal(\n        Uuid const & read_id,\n        gsl::span<std::uint8_t const> const & signal,\n        std::uint32_t sample_count);\n\n    pod5::Result<std::pair<SignalTableRowIndex, SignalTableRowIndex>> add_signal_batch(\n        std::size_t row_count,\n        std::vector<std::shared_ptr<arrow::Array>> && columns,\n        bool final_batch);\n\n    /// \\brief Close this writer, signaling no further data will be written to the writer.\n    Status close();\n\n    /// \\brief Find the signal type of this writer\n    SignalType signal_type() const;\n\n    /// \\brief Reserve space for future row writes, called automatically when a flush occurs.\n    Status reserve_rows();\n\n    /// \\brief Find the schema for the signal table\n    std::shared_ptr<arrow::Schema> const & schema() const { return m_schema; }\n\n    /// \\brief Flush passed data into the writer as a record batch.\n    Status write_batch(arrow::RecordBatch const &);\n\nprivate:\n    /// \\brief Flush buffered data into the writer as a record batch.\n    Status write_batch();\n\n    arrow::MemoryPool * m_pool = nullptr;\n    std::shared_ptr<arrow::Schema> m_schema;\n    SignalTableSchemaDescription m_field_locations;\n    std::shared_ptr<FileOutputStream> m_output_stream;\n    std::size_t m_table_batch_size;\n\n    std::shared_ptr<arrow::ipc::RecordBatchWriter> m_writer;\n\n    std::unique_ptr<arrow::FixedSizeBinaryBuilder> m_read_id_builder;\n    SignalBuilderVariant m_signal_builder;\n    std::unique_ptr<arrow::UInt32Builder> m_samples_builder;\n\n    std::size_t m_written_batched_row_count = 0;\n    std::size_t m_current_batch_row_count = 0;\n};\n\n/// \\brief Make a new writer for a signal table.\n/// \\param sink Sink to be used for output of the table.\n/// \\param metadata Metadata to be applied to the table schema.\n/// \\param table_batch_size The size of each batch written for the table.\n/// \\param pool Pool to be used for building table in memory.\n/// \\returns The writer for the new table.\nPOD5_FORMAT_EXPORT Result<SignalTableWriter> make_signal_table_writer(\n    std::shared_ptr<FileOutputStream> const & sink,\n    std::shared_ptr<arrow::KeyValueMetadata const> const & metadata,\n    std::size_t table_batch_size,\n    SignalType compression_type,\n    arrow::MemoryPool * pool);\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/svb16/common.hpp",
    "content": "#pragma once\n\n#if __cplusplus >= 201703L\n#define SVB16_IF_CONSTEXPR if constexpr\n#else\n#define SVB16_IF_CONSTEXPR if\n#endif\n\n#ifdef _MSC_VER\n#define SVB_RESTRICT __restrict\n#else\n#define SVB_RESTRICT __restrict__\n#endif\n\n#if defined(__x86_64__) || defined(_M_AMD64)  // x64\n#define SVB16_X64\n#elif defined(__arm__) || defined(__aarch64__)\n#define SVB16_ARM\n#endif\n\n#ifndef __has_builtin\n#define __has_builtin(x) 0\n#endif\n\n#if __has_builtin(__builtin_popcount)\n// likely to be a single instruction (POPCNT) on x86_64\n#define svb16_popcount __builtin_popcount\n#else\n// optimising compilers can often convert this pattern to POPCNT on x86_64\ninline int svb16_popcount(unsigned int i)\n{\n    i = i - ((i >> 1) & 0x55555555);                 // add pairs of bits\n    i = (i & 0x33333333) + ((i >> 2) & 0x33333333);  // quads\n    i = (i + (i >> 4)) & 0x0F0F0F0F;                 // groups of 8\n    return (i * 0x01010101) >> 24;                   // horizontal sum of bytes\n}\n#endif\n"
  },
  {
    "path": "c++/pod5_format/svb16/decode.hpp",
    "content": "#pragma once\n\n#include \"common.hpp\"\n#include \"decode_scalar.hpp\"\n#include \"svb16.h\"  // svb16_key_length\n\n#include <type_traits>\n\n#ifdef SVB16_X64\n#include \"decode_x64.hpp\"\n#include \"simd_detect_x64.hpp\"\n#endif\n\nnamespace svb16 {\n\n// Required extra space after readable buffers passed in.\n//\n// Require 1 128 bit buffer beyond the end of all input readable buffers.\ninline std::size_t decode_input_buffer_padding_byte_count()\n{\n#ifdef SVB16_X64\n    return sizeof(__m128i);\n#else\n    return 0;\n#endif\n}\n\ntemplate <typename Int16T, bool UseDelta, bool UseZigzag>\nsize_t decode(gsl::span<Int16T> out, gsl::span<uint8_t const> in, Int16T prev = 0)\n{\n    auto keys_length = ::svb16_key_length(out.size());\n    auto const keys = in.subspan(0, keys_length);\n    auto const data = in.subspan(keys_length);\n#ifdef SVB16_X64\n    if (has_sse4_1()) {\n        return decode_sse<Int16T, UseDelta, UseZigzag>(out, keys, data, prev) - in.begin();\n    }\n#endif\n    return decode_scalar<Int16T, UseDelta, UseZigzag>(out, keys, data, prev) - in.begin();\n}\n\ninline bool validate(gsl::span<uint8_t const> compressed_input, std::size_t out_size)\n{\n    auto const keys_length = ::svb16_key_length(out_size);\n    if (keys_length > compressed_input.size()) {\n        return false;\n    }\n\n    // Pull out the parts of the input data.\n    auto const keys_span = compressed_input.subspan(0, keys_length);\n    auto const data_span = compressed_input.subspan(keys_length);\n    auto keys_ptr = keys_span.begin();\n\n    // Accumulate the key sizes in a wider type to avoid overflow.\n    using Accumulator = std::\n        conditional_t<sizeof(std::size_t) >= sizeof(std::uint64_t), std::size_t, std::uint64_t>;\n    Accumulator encoded_size = 0;\n\n    // Give the compiler a hint that it can avoid branches in the inner loop.\n    for (std::size_t c = 0; c < out_size / 8; c++) {\n        uint8_t const key_byte = *keys_ptr++;\n        for (uint8_t shift = 0; shift < 8; shift++) {\n            uint8_t const code = (key_byte >> shift) & 0x01;\n            encoded_size += code + 1;\n        }\n    }\n    out_size &= 7;\n\n    // Process the remainder one at a time.\n    uint8_t shift = 0;\n    uint8_t key_byte = *keys_ptr++;\n    for (std::size_t c = 0; c < out_size; c++) {\n        if (shift == 8) {\n            shift = 0;\n            key_byte = *keys_ptr++;\n        }\n        uint8_t const code = (key_byte >> shift) & 0x01;\n        encoded_size += code + 1;\n        shift++;\n    }\n\n    return encoded_size == data_span.size();\n}\n\n}  // namespace svb16\n"
  },
  {
    "path": "c++/pod5_format/svb16/decode_scalar.hpp",
    "content": "#pragma once\n\n#include \"common.hpp\"\n\n#include <gsl/gsl-lite.hpp>\n\n#include <cassert>\n#include <cstddef>\n#include <cstdint>\n#include <cstring>\n\nnamespace svb16 {\nnamespace detail {\ninline uint16_t zigzag_decode(uint16_t val)\n{\n    return (val >> 1) ^ static_cast<uint16_t>(0 - (val & 1));\n}\n\ninline uint16_t decode_data(gsl::span<uint8_t const>::iterator & dataPtr, uint8_t code)\n{\n    uint16_t val;\n\n    if (code == 0) {  // 1 byte\n        val = (uint16_t)*dataPtr;\n        dataPtr += 1;\n    } else {  // 2 bytes\n        val = 0;\n        memcpy(&val, dataPtr, 2);  // assumes little endian\n        dataPtr += 2;\n    }\n\n    return val;\n}\n}  // namespace detail\n\ntemplate <typename Int16T, bool UseDelta, bool UseZigzag>\nuint8_t const * decode_scalar(\n    gsl::span<Int16T> out_span,\n    gsl::span<uint8_t const> keys_span,\n    gsl::span<uint8_t const> data_span,\n    Int16T prev = 0)\n{\n    auto const count = out_span.size();\n    if (count == 0) {\n        return data_span.begin();\n    }\n\n    auto out = out_span.begin();\n    auto keys = keys_span.begin();\n    auto data = data_span.begin();\n\n    uint8_t shift = 0;  // cycles 0 through 7 then resets\n    uint8_t key_byte = *keys++;\n    // need to do the arithmetic in unsigned space so it wraps\n    auto u_prev = static_cast<uint16_t>(prev);\n    for (uint32_t c = 0; c < count; c++, shift++) {\n        if (shift == 8) {\n            shift = 0;\n            key_byte = *keys++;\n        }\n        uint16_t value = detail::decode_data(data, (key_byte >> shift) & 0x01);\n        SVB16_IF_CONSTEXPR(UseZigzag) { value = detail::zigzag_decode(value); }\n        SVB16_IF_CONSTEXPR(UseDelta)\n        {\n            value += u_prev;\n            u_prev = value;\n        }\n        *out++ = static_cast<Int16T>(value);\n    }\n\n    assert(out == out_span.end());\n    assert(keys == keys_span.end());\n    assert(data <= data_span.end());\n    return data;\n}\n\n}  // namespace svb16\n"
  },
  {
    "path": "c++/pod5_format/svb16/decode_x64.hpp",
    "content": "#pragma once\n\n#include \"common.hpp\"\n#include \"decode_scalar.hpp\"\n#include \"intrinsics.hpp\"\n#include \"shuffle_tables.hpp\"\n#include \"svb16.h\"  // svb16_key_length\n\n#include <gsl/gsl-lite.hpp>\n\n#include <cstddef>\n#include <cstdint>\n\n#ifdef SVB16_X64\n\nnamespace svb16 {\nnamespace detail {\n[[gnu::target(\"ssse3\")]] inline __m128i zigzag_decode(__m128i val)\n{\n    return _mm_xor_si128(\n        // N >> 1\n        _mm_srli_epi16(val, 1),\n        // 0xFFFF if N & 1 else 0x0000\n        _mm_srai_epi16(_mm_slli_epi16(val, 15), 15)\n        // alternative: _mm_sign_epi16(ones, _mm_slli_epi16(buf, 15))\n    );\n}\n\n[[gnu::target(\"ssse3\")]] inline __m128i unpack(uint32_t key, uint8_t const * SVB_RESTRICT * data)\n{\n    auto const len = static_cast<uint8_t>(8 + svb16_popcount(key));\n    __m128i data_reg = _mm_loadu_si128(reinterpret_cast<__m128i const *>(*data));\n    __m128i const shuffle = *reinterpret_cast<__m128i const *>(&g_decode_shuffle_table[key]);\n\n    data_reg = _mm_shuffle_epi8(data_reg, shuffle);\n    *data += len;\n\n    return data_reg;\n}\n\ntemplate <typename Int16T, bool UseDelta, bool UseZigzag>\n[[gnu::target(\"ssse3\")]] inline void store_8(Int16T * to, __m128i value, __m128i * prev)\n{\n    SVB16_IF_CONSTEXPR(UseZigzag) { value = zigzag_decode(value); }\n\n    SVB16_IF_CONSTEXPR(UseDelta)\n    {\n        auto const broadcast_last_16 =\n            m128i_from_bytes(14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15);\n        // value == [A B C D E F G H] (16 bit values)\n        __m128i add = _mm_slli_si128(value, 2);\n        // add   == [- A B C D E F G]\n        *prev = _mm_shuffle_epi8(*prev, broadcast_last_16);\n        // *prev == [P P P P P P P P]\n        value = _mm_add_epi16(value, add);\n        // value == [A AB BC CD DE FG GH]\n        add = _mm_slli_si128(value, 4);\n        // add   == [- - A AB BC CD DE EF]\n        value = _mm_add_epi16(value, add);\n        // value == [A AB ABC ABCD BCDE CDEF DEFG EFGH]\n        add = _mm_slli_si128(value, 8);\n        // add   == [- - - - A AB ABC ABCD]\n        value = _mm_add_epi16(value, add);\n        // value == [A AB ABC ABCD ABCDE ABCDEF ABCDEFG ABCDEFGH]\n        value = _mm_add_epi16(value, *prev);\n        // value == [PA PAB PABC PABCD PABCDE PABCDEF PABCDEFG PABCDEFGH]\n        *prev = value;\n    }\n\n    _mm_storeu_si128(reinterpret_cast<__m128i *>(to), value);\n}\n}  // namespace detail\n\ntemplate <typename Int16T, bool UseDelta, bool UseZigzag>\n[[gnu::target(\"sse4.1\")]] uint8_t const * decode_sse(\n    gsl::span<Int16T> out_span,\n    gsl::span<uint8_t const> keys_span,\n    gsl::span<uint8_t const> data_span,\n    Int16T prev = 0)\n{\n    auto store_8 = [](Int16T * to, __m128i value, __m128i * prev) {\n        detail::store_8<Int16T, UseDelta, UseZigzag>(to, value, prev);\n    };\n    // this code treats all input as uint16_t (except the zigzag code, which treats it as int16_t)\n    // this isn't a problem, as the scalar code does the same\n\n    auto out = out_span.begin();\n    auto const count = out_span.size();\n    auto keys_it = keys_span.begin();\n    auto data = data_span.begin();\n\n    // handle blocks of 32 values\n    if (count >= 64) {\n        size_t const key_bytes = count / 8;\n\n        __m128i prev_reg;\n        SVB16_IF_CONSTEXPR(UseDelta) { prev_reg = _mm_set1_epi16(prev); }\n\n        int64_t offset = -static_cast<int64_t>(key_bytes) / 8 + 1;  // 8 -> 4?\n        uint64_t const * keyPtr64 = reinterpret_cast<uint64_t const *>(keys_it) - offset;\n        uint64_t nextkeys;\n        memcpy(&nextkeys, keyPtr64 + offset, sizeof(nextkeys));\n\n        __m128i data_reg;\n\n        for (; offset != 0; ++offset) {\n            uint64_t keys = nextkeys;\n            memcpy(&nextkeys, keyPtr64 + offset + 1, sizeof(nextkeys));\n            // faster 16-bit delta since we only have 8-bit values\n            if (!keys) {  // 64 1-byte ints in a row\n\n                // _mm_cvtepu8_epi16: SSE4.1\n                data_reg =\n                    _mm_cvtepu8_epi16(_mm_lddqu_si128(reinterpret_cast<__m128i const *>(data)));\n                store_8(out, data_reg, &prev_reg);\n                data_reg =\n                    _mm_cvtepu8_epi16(_mm_lddqu_si128(reinterpret_cast<__m128i const *>(data + 8)));\n                store_8(out + 8, data_reg, &prev_reg);\n                data_reg = _mm_cvtepu8_epi16(\n                    _mm_lddqu_si128(reinterpret_cast<__m128i const *>(data + 16)));\n                store_8(out + 16, data_reg, &prev_reg);\n                data_reg = _mm_cvtepu8_epi16(\n                    _mm_lddqu_si128(reinterpret_cast<__m128i const *>(data + 24)));\n                store_8(out + 24, data_reg, &prev_reg);\n                data_reg = _mm_cvtepu8_epi16(\n                    _mm_lddqu_si128(reinterpret_cast<__m128i const *>(data + 32)));\n                store_8(out + 32, data_reg, &prev_reg);\n                data_reg = _mm_cvtepu8_epi16(\n                    _mm_lddqu_si128(reinterpret_cast<__m128i const *>(data + +40)));\n                store_8(out + 40, data_reg, &prev_reg);\n                data_reg = _mm_cvtepu8_epi16(\n                    _mm_lddqu_si128(reinterpret_cast<__m128i const *>(data + 48)));\n                store_8(out + 48, data_reg, &prev_reg);\n                data_reg = _mm_cvtepu8_epi16(\n                    _mm_lddqu_si128(reinterpret_cast<__m128i const *>(data + 56)));\n                store_8(out + 56, data_reg, &prev_reg);\n                out += 64;\n                data += 64;\n                continue;\n            }\n\n            data_reg = detail::unpack(keys & 0x00FF, &data);\n            store_8(out, data_reg, &prev_reg);\n            data_reg = detail::unpack((keys & 0xFF00) >> 8, &data);\n            store_8(out + 8, data_reg, &prev_reg);\n\n            keys >>= 16;\n            data_reg = detail::unpack((keys & 0x00FF), &data);\n            store_8(out + 16, data_reg, &prev_reg);\n            data_reg = detail::unpack((keys & 0xFF00) >> 8, &data);\n            store_8(out + 24, data_reg, &prev_reg);\n\n            keys >>= 16;\n            data_reg = detail::unpack((keys & 0x00FF), &data);\n            store_8(out + 32, data_reg, &prev_reg);\n            data_reg = detail::unpack((keys & 0xFF00) >> 8, &data);\n            store_8(out + 40, data_reg, &prev_reg);\n\n            keys >>= 16;\n            data_reg = detail::unpack((keys & 0x00FF), &data);\n            store_8(out + 48, data_reg, &prev_reg);\n\n            // Note we load at least sizeof(__m128i) bytes from the end of data\n            // here, need to ensure that is available to read.\n            //\n            // But we might not use it all depending on the unpacking.\n            //\n            // This is ok due to `decode_input_buffer_padding_byte_count` enuring\n            // extra space on the input buffer.\n            data_reg = detail::unpack((keys & 0xFF00) >> 8, &data);\n            store_8(out + 56, data_reg, &prev_reg);\n\n            out += 64;\n        }\n        {\n            uint64_t keys = nextkeys;\n            // faster 16-bit delta since we only have 8-bit values\n            if (!keys) {  // 64 1-byte ints in a row\n                data_reg =\n                    _mm_cvtepu8_epi16(_mm_lddqu_si128(reinterpret_cast<__m128i const *>(data)));\n                store_8(out, data_reg, &prev_reg);\n                data_reg =\n                    _mm_cvtepu8_epi16(_mm_lddqu_si128(reinterpret_cast<__m128i const *>(data + 8)));\n                store_8(out + 8, data_reg, &prev_reg);\n                data_reg = _mm_cvtepu8_epi16(\n                    _mm_lddqu_si128(reinterpret_cast<__m128i const *>(data + 16)));\n                store_8(out + 16, data_reg, &prev_reg);\n                data_reg = _mm_cvtepu8_epi16(\n                    _mm_lddqu_si128(reinterpret_cast<__m128i const *>(data + 24)));\n                store_8(out + 24, data_reg, &prev_reg);\n                data_reg = _mm_cvtepu8_epi16(\n                    _mm_lddqu_si128(reinterpret_cast<__m128i const *>(data + 32)));\n                store_8(out + 32, data_reg, &prev_reg);\n                data_reg = _mm_cvtepu8_epi16(\n                    _mm_lddqu_si128(reinterpret_cast<__m128i const *>(data + +40)));\n                store_8(out + 40, data_reg, &prev_reg);\n                data_reg = _mm_cvtepu8_epi16(\n                    _mm_lddqu_si128(reinterpret_cast<__m128i const *>(data + 48)));\n                store_8(out + 48, data_reg, &prev_reg);\n                // Only load the first 8 bytes here, otherwise we may run off the end of the buffer\n                data_reg = _mm_cvtepu8_epi16(\n                    _mm_loadl_epi64(reinterpret_cast<__m128i const *>(data + 56)));\n                store_8(out + 56, data_reg, &prev_reg);\n                out += 64;\n                data += 64;\n\n            } else {\n                data_reg = detail::unpack(keys & 0x00FF, &data);\n                store_8(out, data_reg, &prev_reg);\n                data_reg = detail::unpack((keys & 0xFF00) >> 8, &data);\n                store_8(out + 8, data_reg, &prev_reg);\n\n                keys >>= 16;\n                data_reg = detail::unpack((keys & 0x00FF), &data);\n                store_8(out + 16, data_reg, &prev_reg);\n                data_reg = detail::unpack((keys & 0xFF00) >> 8, &data);\n                store_8(out + 24, data_reg, &prev_reg);\n\n                keys >>= 16;\n                data_reg = detail::unpack((keys & 0x00FF), &data);\n                store_8(out + 32, data_reg, &prev_reg);\n                data_reg = detail::unpack((keys & 0xFF00) >> 8, &data);\n                store_8(out + 40, data_reg, &prev_reg);\n\n                keys >>= 16;\n                data_reg = detail::unpack((keys & 0x00FF), &data);\n                store_8(out + 48, data_reg, &prev_reg);\n                data_reg = detail::unpack((keys & 0xFF00) >> 8, &data);\n                store_8(out + 56, data_reg, &prev_reg);\n\n                out += 64;\n            }\n        }\n        prev = out[-1];\n\n        keys_it += key_bytes - (key_bytes & 7);\n    }\n\n    assert(out <= out_span.end());\n    assert(keys_it <= keys_span.end());\n    assert(data <= data_span.end());\n\n    auto out_scalar_span = gsl::make_span(out, out_span.end());\n    assert(out_scalar_span.size() == (count & 63));\n\n    auto keys_scalar_span = gsl::make_span(keys_it, keys_span.end());\n    auto data_scalar_span = gsl::make_span(data, data_span.end());\n\n    return decode_scalar<Int16T, UseDelta, UseZigzag>(\n        out_scalar_span, keys_scalar_span, data_scalar_span, prev);\n}\n\n#endif  // SVB16_X64\n\n}  // namespace svb16\n"
  },
  {
    "path": "c++/pod5_format/svb16/encode.hpp",
    "content": "#pragma once\n\n#include \"common.hpp\"\n#include \"encode_scalar.hpp\"\n#include \"svb16.h\"  // svb16_key_length\n#ifdef SVB16_X64\n#include \"encode_x64.hpp\"\n#include \"simd_detect_x64.hpp\"\n#endif\n\nnamespace svb16 {\n\ntemplate <typename Int16T, bool UseDelta, bool UseZigzag>\nsize_t encode(Int16T const * in, uint8_t * SVB_RESTRICT out, uint32_t count, Int16T prev = 0)\n{\n    auto const keys = out;\n    auto const data = keys + ::svb16_key_length(count);\n#ifdef SVB16_X64\n    if (has_ssse3()) {\n        return encode_sse<Int16T, UseDelta, UseZigzag>(in, keys, data, count, prev) - out;\n    }\n#endif\n    return encode_scalar<Int16T, UseDelta, UseZigzag>(in, keys, data, count, prev) - out;\n}\n\n}  // namespace svb16\n"
  },
  {
    "path": "c++/pod5_format/svb16/encode_scalar.hpp",
    "content": "#pragma once\n\n#include \"common.hpp\"\n\n#include <cstddef>\n#include <cstdint>\n#include <cstring>\n\nnamespace svb16 {\nnamespace detail {\ninline uint16_t zigzag_encode(uint16_t val)\n{\n    return (val + val) ^ static_cast<uint16_t>(static_cast<int16_t>(val) >> 15);\n}\n}  // namespace detail\n\ntemplate <typename Int16T, bool UseDelta, bool UseZigzag>\nuint8_t * encode_scalar(\n    Int16T const * in,\n    uint8_t * SVB_RESTRICT keys,\n    uint8_t * SVB_RESTRICT data,\n    uint32_t count,\n    Int16T prev = 0)\n{\n    if (count == 0) {\n        return data;\n    }\n\n    uint8_t shift = 0;  // cycles 0 through 7 then resets\n    uint8_t key_byte = 0;\n    for (uint32_t c = 0; c < count; c++) {\n        if (shift == 8) {\n            shift = 0;\n            *keys++ = key_byte;\n            key_byte = 0;\n        }\n        uint16_t value;\n        SVB16_IF_CONSTEXPR(UseDelta)\n        {\n            // need to do the arithmetic in unsigned space so it wraps\n            value = static_cast<uint16_t>(in[c]) - static_cast<uint16_t>(prev);\n            SVB16_IF_CONSTEXPR(UseZigzag) { value = detail::zigzag_encode(value); }\n            prev = in[c];\n        }\n        else SVB16_IF_CONSTEXPR(UseZigzag) {\n            value = detail::zigzag_encode(static_cast<uint16_t>(in[c]));\n        }\n        else {\n            value = static_cast<uint16_t>(in[c]);\n        }\n\n        if (value < (1 << 8)) {  // 1 byte\n            *data = static_cast<uint8_t>(value);\n            ++data;\n        } else {                           // 2 bytes\n            std::memcpy(data, &value, 2);  // assumes little endian\n            data += 2;\n            key_byte |= 1 << shift;\n        }\n\n        shift += 1;\n    }\n\n    *keys = key_byte;  // write last key (no increment needed)\n    return data;\n}\n\n}  // namespace svb16\n"
  },
  {
    "path": "c++/pod5_format/svb16/encode_x64.hpp",
    "content": "#pragma once\n\n#include \"common.hpp\"\n#include \"encode_scalar.hpp\"\n#include \"intrinsics.hpp\"\n#include \"shuffle_tables.hpp\"\n#include \"svb16.h\"  // svb16_key_length\n\n#include <cstddef>\n#include <cstdint>\n\n#ifdef SVB16_X64\n\nnamespace svb16 {\nnamespace detail {\n[[gnu::target(\"ssse3\")]] inline __m128i delta(__m128i curr, __m128i prev)\n{\n    return _mm_sub_epi16(curr, _mm_alignr_epi8(curr, prev, 14));\n}\n\n[[gnu::target(\"ssse3\")]] inline __m128i zigzag_encode(__m128i val)\n{\n    return _mm_xor_si128(_mm_add_epi16(val, val), _mm_srai_epi16(val, 16));\n}\n\ntemplate <typename Int16T, bool UseDelta, bool UseZigzag>\n[[gnu::target(\"ssse3\")]] inline __m128i load_8(Int16T const * from, __m128i * prev)\n{\n    auto const loaded = _mm_loadu_si128(reinterpret_cast<__m128i const *>(from));\n    SVB16_IF_CONSTEXPR(UseDelta && UseZigzag)\n    {\n        auto const result = delta(loaded, *prev);\n        *prev = loaded;\n        return zigzag_encode(result);\n    }\n    else SVB16_IF_CONSTEXPR(UseDelta) {\n        auto const result = delta(loaded, *prev);\n        *prev = loaded;\n        return result;\n    }\n    else SVB16_IF_CONSTEXPR(UseZigzag) {\n        return zigzag_encode(loaded);\n    }\n    else {\n        return loaded;\n    }\n}\n}  // namespace detail\n\ntemplate <typename Int16T, bool UseDelta, bool UseZigzag>\n[[gnu::target(\"ssse3\")]] uint8_t * encode_sse(\n    Int16T const * in,\n    uint8_t * SVB_RESTRICT keys_dest,\n    uint8_t * SVB_RESTRICT data_dest,\n    uint32_t count,\n    Int16T prev = 0)\n{\n    // this code treats all input as uint16_t (except the zigzag code, which treats it as int16_t)\n    // this isn't a problem, as the scalar code does the same\n    __m128i prev_reg;\n    SVB16_IF_CONSTEXPR(UseDelta) { prev_reg = _mm_set1_epi16(prev); }\n    //auto const key_len = svb16_key_length(count);\n    auto const mask_01 = detail::m128i_from_bytes(\n        0x01,\n        0x01,\n        0x01,\n        0x01,\n        0x01,\n        0x01,\n        0x01,\n        0x01,\n        0x01,\n        0x01,\n        0x01,\n        0x01,\n        0x01,\n        0x01,\n        0x01,\n        0x01);\n    for (Int16T const * end = &in [(count & ~15)]; in != end; in += 16) {\n        // load up 16 values into r0 and r1\n        auto r0 = detail::load_8<Int16T, UseDelta, UseZigzag>(in, &prev_reg);\n        auto r1 = detail::load_8<Int16T, UseDelta, UseZigzag>(in + 8, &prev_reg);\n\n        // 1 byte per input byte: 1 if the byte is set, 0 if not\n        auto r2 = _mm_min_epu8(mask_01, r0);\n        auto r3 = _mm_min_epu8(mask_01, r1);\n        // 1 byte per input Int16T: FF if the MSB is set, 00 or 01 if not\n        // (us = unsigned saturation)\n        r2 = _mm_packus_epi16(r2, r3);\n        // 1 bit per input Int16T: 1 if the MSB is set, 0 if not\n        // only the low 16 bits are set\n        auto const keys = static_cast<uint16_t>(_mm_movemask_epi8(r2));\n\n        // use the shuffle table to discard the MSB if the corresponidng key bit is not set\n        r2 = _mm_loadu_si128((__m128i *)&g_encode_shuffle_table[(keys << 4) & 0x07F0]);\n        r3 = _mm_loadu_si128((__m128i *)&g_encode_shuffle_table[(keys >> 4) & 0x07F0]);\n        r0 = _mm_shuffle_epi8(r0, r2);\n        r1 = _mm_shuffle_epi8(r1, r3);\n\n        // store the data to data_dest (note that we often end up with overlapping writes)\n        _mm_storeu_si128(reinterpret_cast<__m128i *>(data_dest), r0);\n        data_dest += 8 + svb16_popcount(keys & 0xFF);\n        _mm_storeu_si128(reinterpret_cast<__m128i *>(data_dest), r1);\n        data_dest += 8 + svb16_popcount(keys >> 8);\n\n        *reinterpret_cast<uint16_t *>(keys_dest) = keys;\n        keys_dest += 2;\n    }\n\n    SVB16_IF_CONSTEXPR(UseDelta) { prev = _mm_extract_epi16(prev_reg, 7); }\n    // max two control bytes (16 values) left, use the scalar function\n    count &= 15;\n    return encode_scalar<Int16T, UseDelta, UseZigzag>(in, keys_dest, data_dest, count, prev);\n}\n\n#endif  // SVB16_X64\n\n}  // namespace svb16\n"
  },
  {
    "path": "c++/pod5_format/svb16/generate_shuffle_tables.py",
    "content": "def encode_table_row(control):\n    table = []\n    for i in range(7):\n        offset = i * 2\n        # first byte\n        table.append(offset)\n        if (control >> i) & 1:\n            table.append(offset + 1)\n    final_offset = 14\n    for j in range(2):\n        table.append(final_offset + j)\n    for i in range(16 - len(table)):\n        table.append(0xFF)\n    return table\n\n\ndef decode_table_row(control):\n    table = []\n    offset = 0\n    for i in range(8):\n        table.append(offset)\n        offset += 1\n        if (control >> i) & 1:\n            table.append(offset)\n            offset += 1\n        else:\n            table.append(0xFF)\n    return table\n\n\ndef print_x64_encode_table():\n    print(\"static constexpr uint8_t g_encode_shuffle_table[128*16] = {\")\n    for i in range(128):\n        table = encode_table_row(i)\n        print(\"\\t\", \", \".join(f\"0x{v:02X}\" for v in table), \",\", sep=\"\")\n    print(\"};\\n\\n\")\n\n\ndef print_x64_decode_table():\n    print(\"static const uint8_t g_decode_shuffle_table[256][16] = {\")\n    for i in range(256):\n        table = decode_table_row(i)\n        print(\"\\t{ \", \", \".join(f\"0x{v:02X}\" for v in table), \"},\", sep=\"\")\n    print(\"};\\n\\n\")\n\n\nif __name__ == \"__main__\":\n    print(\"#pragma once\")\n    print('#include \"common.hpp\" // arch macros')\n    print(\"#include <cstdint>\")\n    print()\n    print(\"#ifdef SVB16_X64\")\n    print_x64_encode_table()\n    print_x64_decode_table()\n    print(\"#endif\")\n"
  },
  {
    "path": "c++/pod5_format/svb16/intrinsics.hpp",
    "content": "#pragma once\n\n#include \"common.hpp\"  // architecture macros\n\n#if defined(_MSC_VER)\n#include <intrin.h>\n#elif defined(__GNUC__) && defined(SVB16_X64)\n#include <x86intrin.h>\n#elif defined(__GNUC__) && defined(__ARM_NEON__)\n#include <arm_neon.h>\n#endif\n\n#include <cstdint>\n\nnamespace svb16 { namespace detail {\n[[gnu::target(\"sse2\")]] inline constexpr __m128i m128i_from_bytes(\n    uint8_t a,\n    uint8_t b,\n    uint8_t c,\n    uint8_t d,\n    uint8_t e,\n    uint8_t f,\n    uint8_t g,\n    uint8_t h,\n    uint8_t i,\n    uint8_t j,\n    uint8_t k,\n    uint8_t l,\n    uint8_t m,\n    uint8_t n,\n    uint8_t o,\n    uint8_t p)\n{\n#ifdef _MSC_VER\n    return __m128i{\n        (char)a,\n        (char)b,\n        (char)c,\n        (char)d,\n        (char)e,\n        (char)f,\n        (char)g,\n        (char)h,\n        (char)i,\n        (char)j,\n        (char)k,\n        (char)l,\n        (char)m,\n        (char)n,\n        (char)o,\n        (char)p};\n#else\n    return __m128i{\n        static_cast<int64_t>(static_cast<uint64_t>(h) << 56) + (static_cast<int64_t>(g) << 48)\n            + (static_cast<int64_t>(f) << 40) + (static_cast<int64_t>(e) << 32)\n            + (static_cast<int64_t>(d) << 24) + (static_cast<int64_t>(c) << 16)\n            + (static_cast<int64_t>(b) << 8) + static_cast<int64_t>(a),\n        static_cast<int64_t>(static_cast<uint64_t>(h) << 56) + (static_cast<int64_t>(g) << 48)\n            + (static_cast<int64_t>(f) << 40) + (static_cast<int64_t>(e) << 32)\n            + (static_cast<int64_t>(d) << 24) + (static_cast<int64_t>(c) << 16)\n            + (static_cast<int64_t>(b) << 8) + static_cast<int64_t>(a)};\n#endif\n}\n}}  // namespace svb16::detail\n"
  },
  {
    "path": "c++/pod5_format/svb16/shuffle_tables.hpp",
    "content": "#pragma once\n#include \"common.hpp\"  // arch macros\n\n#include <cstdint>\n\n#ifdef SVB16_X64\nstatic constexpr uint8_t g_encode_shuffle_table[128 * 16] = {\n    0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF,\n    0x00, 0x02, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF,\n    0x00, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF,\n    0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF,\n    0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,\n};\n\nstatic uint8_t const g_decode_shuffle_table[256][16] = {\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF,\n     0x0C,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF,\n     0x0D,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C,\n     0x0D,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D,\n     0xFF},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D,\n     0x0E,\n     0xFF},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0xFF,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0xFF,\n     0x0C,\n     0x0D},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0xFF,\n     0x0D,\n     0x0E},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0xFF,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0xFF,\n     0x0B,\n     0x0C,\n     0x0D,\n     0x0E},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0xFF,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0xFF,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D,\n     0x0E},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0xFF,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0xFF,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D,\n     0x0E},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0xFF,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0xFF,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D,\n     0x0E},\n    {0x00,\n     0xFF,\n     0x01,\n     0xFF,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D},\n    {0x00,\n     0x01,\n     0x02,\n     0xFF,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D,\n     0x0E},\n    {0x00,\n     0xFF,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D,\n     0x0E},\n    {0x00,\n     0x01,\n     0x02,\n     0x03,\n     0x04,\n     0x05,\n     0x06,\n     0x07,\n     0x08,\n     0x09,\n     0x0A,\n     0x0B,\n     0x0C,\n     0x0D,\n     0x0E,\n     0x0F},\n};\n\n#endif\n"
  },
  {
    "path": "c++/pod5_format/svb16/simd_detect_x64.hpp",
    "content": "#pragma once\n\n#include \"common.hpp\"  // architecture macros\n\n#if defined(SVB16_X64)\n\n#ifdef _MSC_VER\n#include <intrin.h>\n#endif\n\n// __AVX__ is documented for MSVC, but __SSE4_1__ isn't\n#if defined(__AVX__) || defined(__SSE4_1__)\n\ninline constexpr bool has_ssse3() { return true; }\n\ninline constexpr bool has_sse4_1() { return true; }\n\n#else\n\nstruct CpuidResult {\n    unsigned int eax;\n    unsigned int ebx;\n    unsigned int ecx;\n    unsigned int edx;\n};\n\ninline CpuidResult cpuid(unsigned int leaf, unsigned int subleaf)\n{\n#ifdef _MSC_VER\n    int info[4];\n    __cpuidex(info, static_cast<int>(leaf), static_cast<int>(subleaf));\n    return CpuidResult{\n        static_cast<unsigned int>(info[0]),\n        static_cast<unsigned int>(info[1]),\n        static_cast<unsigned int>(info[2]),\n        static_cast<unsigned int>(info[3]),\n    };\n#else\n    CpuidResult info;\n    asm(\"cpuid\\n\\t\"\n        : \"=a\"(info.eax), \"=b\"(info.ebx), \"=c\"(info.ecx), \"=d\"(info.edx)\n        : \"0\"(leaf), \"2\"(subleaf));\n    return info;\n#endif\n}\n\ninline unsigned int cpuid_leaf1_ecx()\n{\n    // using C++11 atomic static variables\n    static unsigned int const ecx = cpuid(1, 0).ecx;\n    return ecx;\n}\n\n#if defined(__SSSE3__)\ninline constexpr bool has_ssse3() { return true; }\n#else\ninline bool has_ssse3() { return (cpuid_leaf1_ecx() & (1 << 9)) != 0; }\n#endif\n\ninline bool has_sse4_1() { return (cpuid_leaf1_ecx() & (1 << 19)) != 0; }\n\n#endif  // defined(__SSE4_1__)\n#endif  // defined(SVB16_X64)\n"
  },
  {
    "path": "c++/pod5_format/svb16/streamvbytedelta_decode_16.c",
    "content": "#include \"streamvbyte_isadetection.h\"\n#include \"streamvbytedelta.h\"\n\n#include <string.h>  // for memcpy\n\nstatic inline uint16_t zigzag_decode_16(uint16_t val)\n{\n    return (val >> 1) ^ (uint16_t)(0 - (val & 1));\n}\n\nstatic inline uint16_t _decode_data(uint8_t const ** dataPtrPtr, uint8_t code)\n{\n    uint8_t const * dataPtr = *dataPtrPtr;\n    uint16_t val;\n\n    if (code == 0) {  // 1 byte\n        val = (uint16_t)*dataPtr;\n        dataPtr += 1;\n    } else {  // 2 bytes\n        val = 0;\n        memcpy(&val, dataPtr, 2);  // assumes little endian\n        dataPtr += 2;\n    }\n\n    *dataPtrPtr = dataPtr;\n    return val;\n}\n\nstatic uint8_t const * svb_decode_scalar_d1_init(\n    uint16_t * outPtr,\n    uint8_t const * keyPtr,\n    uint8_t const * dataPtr,\n    uint32_t count,\n    uint16_t prev)\n{\n    if (count == 0) {\n        return dataPtr;  // no reads or writes if no data\n    }\n\n    uint8_t shift = 0;\n    uint16_t key = *keyPtr++;\n\n    for (uint32_t c = 0; c < count; c++) {\n        if (shift == 8) {\n            shift = 0;\n            key = *keyPtr++;\n        }\n        uint16_t val = zigzag_decode_16(_decode_data(&dataPtr, (key >> shift) & 0x1));\n        //uint16_t val = _decode_data(&dataPtr, (key >> shift) & 0x1);\n        val += prev;\n        *outPtr++ = val;\n        prev = val;\n        shift += 1;\n    }\n\n    return dataPtr;  // pointer to first unused byte after end\n}\n\n#ifdef STREAMVBYTE_X64\n#include \"streamvbytedelta_x64_decode_16.c\"\n#endif\n\nsize_t streamvbyte_zigzag_delta_decode_16(\n    uint8_t const * in,\n    uint16_t * out,\n    uint32_t count,\n    uint16_t prev)\n{\n    // keyLen = ceil(count / 8), without overflowing (1 bit per input value):\n    uint32_t keyLen = (count >> 3) + (((count & 7) + 7) >> 3);\n    uint8_t const * keyPtr = in;\n    uint8_t const * dataPtr = keyPtr + keyLen;  // data starts at end of keys\n#ifdef STREAMVBYTE_X64\n    if (streamvbyte_ssse3()) {\n        return svb_decode_avx_d1_init(out, keyPtr, dataPtr, count, prev) - in;\n    }\n#endif\n    return svb_decode_scalar_d1_init(out, keyPtr, dataPtr, count, prev) - in;\n}\n"
  },
  {
    "path": "c++/pod5_format/svb16/streamvbytedelta_encode_16.c",
    "content": "#include \"streamvbyte_isadetection.h\"\n#include \"streamvbytedelta.h\"\n\n#include <stdio.h>\n#include <string.h>  // for memcpy\n\n#ifdef STREAMVBYTE_X64\n#include \"streamvbytedelta_x64_encode_16.c\"\n#endif\n\nstatic inline uint16_t _zigzag_encode_16(uint16_t val)\n{\n    return (val + val) ^ ((int16_t)val >> 15);\n}\n\nstatic uint8_t _encode_data(uint16_t val, uint8_t * __restrict__ * dataPtrPtr)\n{\n    uint8_t * dataPtr = *dataPtrPtr;\n    uint8_t code;\n\n    if (val < (1 << 8)) {  // 1 byte\n        *dataPtr = (uint8_t)(val);\n        *dataPtrPtr += 1;\n        code = 0;\n    } else {                       // 2 bytes\n        memcpy(dataPtr, &val, 2);  // assumes little endian\n        *dataPtrPtr += 2;\n        code = 1;\n    }\n\n    return code;\n}\n\nstatic uint8_t * svb_encode_scalar_d1_init(\n    uint16_t const * in,\n    uint8_t * __restrict__ keyPtr,\n    uint8_t * __restrict__ dataPtr,\n    uint32_t count,\n    uint16_t prev)\n{\n    if (count == 0) {\n        return dataPtr;  // exit immediately if no data\n    }\n\n    uint8_t shift = 0;  // cycles 0 through 7 then resets\n    uint8_t key = 0;\n    for (uint32_t c = 0; c < count; c++) {\n        if (shift == 8) {\n            shift = 0;\n            *keyPtr++ = key;\n            key = 0;\n        }\n        uint16_t val = _zigzag_encode_16((uint16_t)(in[c] - prev));\n        //uint16_t val = in[c] - prev;\n        prev = in[c];\n        uint8_t code = _encode_data(val, &dataPtr);\n        key |= code << shift;\n        shift += 1;\n    }\n\n    *keyPtr = key;   // write last key (no increment needed)\n    return dataPtr;  // pointer to first unused data byte\n}\n\nsize_t streamvbyte_zigzag_delta_encode_16(\n    uint16_t const * in,\n    uint32_t count,\n    uint8_t * out,\n    uint16_t prev)\n{\n#ifdef STREAMVBYTE_X64\n    if (streamvbyte_ssse3()) {\n        return streamvbyte_zigzag_delta_encode_SSSE3_d1_init(in, count, out, prev);\n    }\n#endif\n    uint8_t * keyPtr = out;  // keys come at start\n    // keyLen = ceil(count / 8), without overflowing (1 bit per input value):\n    uint32_t keyLen = (count >> 3) + (((count & 7) + 7) >> 3);\n    uint8_t * dataPtr = keyPtr + keyLen;  // variable byte data after all keys\n    return svb_encode_scalar_d1_init(in, keyPtr, dataPtr, count, prev) - out;\n}\n"
  },
  {
    "path": "c++/pod5_format/svb16/streamvbytedelta_x64_decode_16.c",
    "content": "#include \"streamvbyte_isadetection.h\"\n#include \"streamvbyte_shuffle_tables_decode_16.h\"\n\n#include <string.h>  // for memcpy\n#ifdef STREAMVBYTE_X64\n\nSTREAMVBYTE_TARGET_SSSE3\nstatic __m128i undo_zigzag_16(__m128i buf)\n{\n    return _mm_xor_si128(\n        // N >> 1\n        _mm_srli_epi16(buf, 1),\n        // 0xFFFF if N & 1 else 0x0000\n        _mm_srai_epi16(_mm_slli_epi16(buf, 15), 15)\n        // alternative: _mm_sign_epi16(ones, _mm_slli_epi16(buf, 15))\n    );\n}\n\nSTREAMVBYTE_UNTARGET_REGION\n\nSTREAMVBYTE_TARGET_SSSE3\nstatic inline __m128i _decode_avx(uint32_t key, uint8_t const * __restrict__ * dataPtrPtr)\n{\n    uint8_t len = 8 + popcount(key);\n    __m128i Data = _mm_loadu_si128((__m128i *)*dataPtrPtr);\n    __m128i Shuf = *(__m128i *)&shuffleTable[key];\n\n    Data = _mm_shuffle_epi8(Data, Shuf);\n    *dataPtrPtr += len;\n\n    return Data;\n}\n\nSTREAMVBYTE_UNTARGET_REGION\n\nSTREAMVBYTE_TARGET_SSSE3\nstatic inline void _write_avx(uint16_t * out, __m128i Vec)\n{\n    _mm_storeu_si128((__m128i *)out, Vec);\n}\n\nSTREAMVBYTE_UNTARGET_REGION\n\nSTREAMVBYTE_TARGET_SSSE3\nstatic inline __m128i _write_16bit_avx_d1(uint16_t * out, __m128i Vec, __m128i Prev)\n{\n#ifndef _MSC_VER\n    __m128i BroadcastLast16 = {0x0F0E0F0E0F0E0F0E, 0x0F0E0F0E0F0E0F0E};\n#else\n    __m128i BroadcastLast16 = {14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15};\n#endif\n    Vec = undo_zigzag_16(Vec);\n    // vec == [A B C D E F G H] (16 bit values)\n    __m128i Add = _mm_slli_si128(Vec, 2);            // [- A B C D E F G]\n    Prev = _mm_shuffle_epi8(Prev, BroadcastLast16);  // [P P P P P P P P]\n    Vec = _mm_add_epi16(Vec, Add);                   // [A AB BC CD DE FG GH]\n    Add = _mm_slli_si128(Vec, 4);                    // [- - A AB BC CD DE EF]\n    Vec = _mm_add_epi16(Vec, Add);                   // [A AB ABC ABCD BCDE CDEF DEFG EFGH]\n    Add = _mm_slli_si128(Vec, 8);                    // [- - - - A AB ABC ABCD]\n    Vec = _mm_add_epi16(Vec, Add);   // [A AB ABC ABCD ABCDE ABCDEF ABCDEFG ABCDEFGH]\n    Vec = _mm_add_epi16(Vec, Prev);  // [PA PAB PABC PABCD PABCDE PABCDEF PABCDEFG PABCDEFGH]\n    _write_avx(out, Vec);\n    return Vec;\n}\n\nSTREAMVBYTE_UNTARGET_REGION\n\nSTREAMVBYTE_TARGET_SSSE3\nstatic uint8_t const * svb_decode_avx_d1_init(\n    uint16_t * out,\n    uint8_t const * __restrict__ keyPtr,\n    uint8_t const * __restrict__ dataPtr,\n    uint64_t count,\n    uint16_t prev)\n{\n    uint64_t keybytes = count / 4;  // number of key bytes\n    if (keybytes >= 8) {\n        __m128i Prev = _mm_set1_epi16(prev);\n        __m128i Data;\n\n        int64_t Offset = -(int64_t)keybytes / 8 + 1;\n\n        uint64_t const * keyPtr64 = (uint64_t const *)keyPtr - Offset;\n        uint64_t nextkeys;\n        memcpy(&nextkeys, keyPtr64 + Offset, sizeof(nextkeys));\n        for (; Offset != 0; ++Offset) {\n            uint64_t keys = nextkeys;\n            memcpy(&nextkeys, keyPtr64 + Offset + 1, sizeof(nextkeys));\n            // faster 16-bit delta since we only have 8-bit values\n            if (!keys) {  // 32 1-byte ints in a row\n\n                // _mm_cvtepu8_epi16: SSE4.1\n                Data = _mm_cvtepu8_epi16(_mm_lddqu_si128((__m128i *)(dataPtr)));\n                Prev = _write_16bit_avx_d1(out, Data, Prev);\n                Data = _mm_cvtepu8_epi16(_mm_lddqu_si128((__m128i *)(dataPtr + 8)));\n                Prev = _write_16bit_avx_d1(out + 8, Data, Prev);\n                Data = _mm_cvtepu8_epi16(_mm_lddqu_si128((__m128i *)(dataPtr + 16)));\n                Prev = _write_16bit_avx_d1(out + 16, Data, Prev);\n                Data = _mm_cvtepu8_epi16(_mm_lddqu_si128((__m128i *)(dataPtr + 24)));\n                Prev = _write_16bit_avx_d1(out + 24, Data, Prev);\n                out += 32;\n                dataPtr += 32;\n                continue;\n            }\n\n            Data = _decode_avx(keys & 0x00FF, &dataPtr);\n            Prev = _write_16bit_avx_d1(out, Data, Prev);\n            Data = _decode_avx((keys & 0xFF00) >> 8, &dataPtr);\n            Prev = _write_16bit_avx_d1(out + 4, Data, Prev);\n\n            keys >>= 16;\n            Data = _decode_avx((keys & 0x00FF), &dataPtr);\n            Prev = _write_16bit_avx_d1(out + 8, Data, Prev);\n            Data = _decode_avx((keys & 0xFF00) >> 8, &dataPtr);\n            Prev = _write_16bit_avx_d1(out + 12, Data, Prev);\n\n            keys >>= 16;\n            Data = _decode_avx((keys & 0x00FF), &dataPtr);\n            Prev = _write_16bit_avx_d1(out + 16, Data, Prev);\n            Data = _decode_avx((keys & 0xFF00) >> 8, &dataPtr);\n            Prev = _write_16bit_avx_d1(out + 20, Data, Prev);\n\n            keys >>= 16;\n            Data = _decode_avx((keys & 0x00FF), &dataPtr);\n            Prev = _write_16bit_avx_d1(out + 24, Data, Prev);\n            Data = _decode_avx((keys & 0xFF00) >> 8, &dataPtr);\n            Prev = _write_16bit_avx_d1(out + 28, Data, Prev);\n\n            out += 32;\n        }\n        {\n            uint64_t keys = nextkeys;\n            // faster 16-bit delta since we only have 8-bit values\n            if (!keys) {  // 32 1-byte ints in a row\n                Data = _mm_cvtepu8_epi16(_mm_lddqu_si128((__m128i *)(dataPtr)));\n                Prev = _write_16bit_avx_d1(out, Data, Prev);\n                Data = _mm_cvtepu8_epi16(_mm_lddqu_si128((__m128i *)(dataPtr + 8)));\n                Prev = _write_16bit_avx_d1(out + 8, Data, Prev);\n                Data = _mm_cvtepu8_epi16(_mm_lddqu_si128((__m128i *)(dataPtr + 16)));\n                Prev = _write_16bit_avx_d1(out + 16, Data, Prev);\n                Data = _mm_cvtepu8_epi16(_mm_loadl_epi64((__m128i *)(dataPtr + 24)));\n                Prev = _write_16bit_avx_d1(out + 24, Data, Prev);\n                out += 32;\n                dataPtr += 32;\n\n            } else {\n                Data = _decode_avx(keys & 0x00FF, &dataPtr);\n                Prev = _write_16bit_avx_d1(out, Data, Prev);\n                Data = _decode_avx((keys & 0xFF00) >> 8, &dataPtr);\n                Prev = _write_16bit_avx_d1(out + 4, Data, Prev);\n\n                keys >>= 16;\n                Data = _decode_avx((keys & 0x00FF), &dataPtr);\n                Prev = _write_16bit_avx_d1(out + 8, Data, Prev);\n                Data = _decode_avx((keys & 0xFF00) >> 8, &dataPtr);\n                Prev = _write_16bit_avx_d1(out + 12, Data, Prev);\n\n                keys >>= 16;\n                Data = _decode_avx((keys & 0x00FF), &dataPtr);\n                Prev = _write_16bit_avx_d1(out + 16, Data, Prev);\n                Data = _decode_avx((keys & 0xFF00) >> 8, &dataPtr);\n                Prev = _write_16bit_avx_d1(out + 20, Data, Prev);\n\n                keys >>= 16;\n                Data = _decode_avx((keys & 0x00FF), &dataPtr);\n                Prev = _write_16bit_avx_d1(out + 24, Data, Prev);\n                Data = _decode_avx((keys & 0xFF00) >> 8, &dataPtr);\n                Prev = _write_16bit_avx_d1(out + 28, Data, Prev);\n\n                out += 32;\n            }\n        }\n        prev = out[-1];\n    }\n    uint64_t consumedkeys = keybytes - (keybytes & 7);\n    return svb_decode_scalar_d1_init(out, keyPtr + consumedkeys, dataPtr, count & 31, prev);\n}\n\nSTREAMVBYTE_UNTARGET_REGION\n#endif\n"
  },
  {
    "path": "c++/pod5_format/svb16/streamvbytedelta_x64_encode_16.c",
    "content": "\n#include \"streamvbyte_isadetection.h\"\n#include \"streamvbyte_shuffle_tables_encode_16.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#ifdef STREAMVBYTE_X64\n\nSTREAMVBYTE_TARGET_SSSE3\nstatic __m128i Delta(__m128i curr, __m128i prev)\n{\n    // _mm_alignr_epi8: SSSE3\n    return _mm_sub_epi16(curr, _mm_alignr_epi8(curr, prev, 14));\n}\n\nSTREAMVBYTE_UNTARGET_REGION\n\nSTREAMVBYTE_TARGET_SSSE3\nstatic __m128i zigzag_16(__m128i buf)\n{\n    return _mm_xor_si128(_mm_add_epi16(buf, buf), _mm_srai_epi16(buf, 16));\n}\n\nSTREAMVBYTE_UNTARGET_REGION\n\n// based on code by aqrit  (streamvbyte_encode_SSSE3)\nSTREAMVBYTE_TARGET_SSSE3\nsize_t streamvbyte_zigzag_delta_encode_SSSE3_d1_init(\n    uint16_t const * in,\n    uint32_t count,\n    uint8_t * out,\n    uint16_t prev)\n{\n    __m128i Prev = _mm_set1_epi16(prev);\n    uint32_t keyLen = (count >> 3) + (((count & 7) + 7) >> 3);  // 1-bit rounded to full byte\n    uint8_t * restrict keyPtr = &out[0];\n    uint8_t * restrict dataPtr = &out[keyLen];  // variable length data after keys\n\n    __m128i const mask_01 = _mm_set1_epi8(0x01);\n\n    for (uint16_t const * end = &in [(count & ~15)]; in != end; in += 16) {\n        __m128i rawr0, r0, rawr1, r1, r2, r3;\n        size_t keys;\n\n        rawr0 = _mm_loadu_si128((__m128i *)&in[0]);\n        r0 = zigzag_16(Delta(rawr0, Prev));\n        Prev = rawr0;\n        rawr1 = _mm_loadu_si128((__m128i *)&in[8]);\n        r1 = zigzag_16(Delta(rawr1, Prev));\n        Prev = rawr1;\n\n        // 1 if the byte is set, 0 if not\n        r2 = _mm_min_epu8(mask_01, r0);\n        r3 = _mm_min_epu8(mask_01, r1);\n        // for each (u)int16, FF if the MSB is set, 00 or 01 if not (us = unsigned saturation)\n        r2 = _mm_packus_epi16(r2, r3);\n        // for each byte, store a bit: 1 if FF, 0 if 00 or 01 (so 1 if MSB is set, 0 if not)\n        keys = (size_t)_mm_movemask_epi8(r2);\n\n        r2 = _mm_loadu_si128((__m128i *)&shuf_lut[(keys << 4) & 0x07F0]);\n        r3 = _mm_loadu_si128((__m128i *)&shuf_lut[(keys >> 4) & 0x07F0]);\n        // _mm_shuffle_epi8: SSSE3\n        r0 = _mm_shuffle_epi8(r0, r2);\n        r1 = _mm_shuffle_epi8(r1, r3);\n\n        _mm_storeu_si128((__m128i *)dataPtr, r0);\n        dataPtr += 8 + popcount(keys & 0xFF);\n        _mm_storeu_si128((__m128i *)dataPtr, r1);\n        dataPtr += 8 + popcount(keys >> 8);\n\n        *((uint16_t *)keyPtr) = (uint16_t)keys;\n        keyPtr += 2;\n    }\n    prev = _mm_extract_epi16(Prev, 7);\n\n    // do remaining - max two control bytes left\n    uint16_t key = 0;\n    for (size_t i = 0; i < (count & 15); i++) {\n        // TODO: can we factor this out to reuse the non-intrinsic code?\n        uint16_t dw = in[i] - prev;\n        prev = in[i];\n        uint16_t zz = (dw + dw) ^ ((int16_t)dw >> 15);\n        uint16_t symbol = (zz > 0x00FF);\n        key |= symbol << (i + i);\n        *((uint16_t *)dataPtr) = zz;\n        dataPtr += 1 + symbol;\n    }\n    memcpy(keyPtr, &key, ((count & 15) + 5) >> 3);\n\n    return dataPtr - out;\n}\n\nSTREAMVBYTE_UNTARGET_REGION\n#endif\n"
  },
  {
    "path": "c++/pod5_format/svb16/svb16.c",
    "content": ""
  },
  {
    "path": "c++/pod5_format/svb16/svb16.h",
    "content": "#ifndef SVB16_H\n#define SVB16_H\n\n#include <stdint.h>\n\n#if defined(__cplusplus)\nextern \"C\" {\n#endif\n\n/// Get the number of key bytes required to encode a given number of 16-bit integers.\ninline uint32_t svb16_key_length(uint32_t count)\n{\n    // ceil(count / 8.0), without overflowing or using fp arithmetic\n    return (count >> 3) + (((count & 7) + 7) >> 3);\n}\n\n/// Get the maximum number of bytes required to encode a given number of 16-bit integers.\ninline uint32_t svb16_max_encoded_length(uint32_t count)\n{\n    return svb16_key_length(count) + (2 * count);\n}\n\n#if defined(__cplusplus)\n};\n#endif\n\n#endif  // SVB16_H\n"
  },
  {
    "path": "c++/pod5_format/table_reader.cpp",
    "content": "#include \"pod5_format/table_reader.h\"\n\n#include <arrow/ipc/reader.h>\n#include <arrow/record_batch.h>\n#include <arrow/util/align_util.h>\n\nnamespace pod5 {\n\nTableRecordBatch::TableRecordBatch(std::shared_ptr<arrow::RecordBatch> const & batch)\n: m_batch(batch)\n{\n}\n\nTableRecordBatch::TableRecordBatch(std::shared_ptr<arrow::RecordBatch> && batch)\n: m_batch(std::move(batch))\n{\n}\n\nTableRecordBatch::TableRecordBatch(TableRecordBatch const &) = default;\nTableRecordBatch & TableRecordBatch::operator=(TableRecordBatch const &) = default;\nTableRecordBatch::TableRecordBatch(TableRecordBatch &&) = default;\nTableRecordBatch & TableRecordBatch::operator=(TableRecordBatch &&) = default;\nTableRecordBatch::~TableRecordBatch() = default;\n\nstd::size_t TableRecordBatch::num_rows() const { return m_batch->num_rows(); }\n\n//---------------------------------------------------------------------------------------------------------------------\n\nTableReader::TableReader(\n    std::shared_ptr<void> && input_source,\n    std::shared_ptr<arrow::ipc::RecordBatchFileReader> && reader,\n    SchemaMetadataDescription && schema_metadata,\n    arrow::MemoryPool * pool)\n: m_input_source(std::move(input_source))\n, m_reader(std::move(reader))\n, m_schema_metadata(std::move(schema_metadata))\n{\n}\n\nTableReader::TableReader(TableReader &&) = default;\nTableReader & TableReader::operator=(TableReader &&) = default;\nTableReader::~TableReader() = default;\n\nstd::size_t TableReader::num_record_batches() const { return m_reader->num_record_batches(); }\n\nResult<int64_t> TableReader::CountRows() const { return m_reader->CountRows(); }\n\nResult<std::shared_ptr<arrow::RecordBatch>> TableReader::ReadRecordBatch(int i) const\n{\n    return ReadRecordBatchAndValidate(*m_reader, i);\n}\n\nResult<std::shared_ptr<arrow::RecordBatch>> ReadRecordBatchAndValidate(\n    arrow::ipc::RecordBatchFileReader & reader,\n    int i)\n{\n    ARROW_ASSIGN_OR_RAISE(auto batch, reader.ReadRecordBatch(i));\n    ARROW_RETURN_NOT_OK(batch->ValidateFull());\n\n    // Check that the data buffers are aligned.\n    std::vector<bool> unaligned_columns;\n    unaligned_columns.reserve(batch->num_columns());\n    if (!arrow::util::CheckAlignment(*batch, arrow::util::kValueAlignment, &unaligned_columns)) {\n        return Status::Invalid(\"Column data alignment check failed\");\n    }\n\n    return batch;\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/table_reader.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/schema_metadata.h\"\n\n#include <memory>\n\nnamespace arrow {\nclass MemoryPool;\nclass RecordBatch;\n\nnamespace ipc {\nclass RecordBatchFileReader;\n}\n}  // namespace arrow\n\nnamespace pod5 {\n\nclass POD5_FORMAT_EXPORT TableRecordBatch {\npublic:\n    TableRecordBatch(std::shared_ptr<arrow::RecordBatch> const & batch);\n    TableRecordBatch(std::shared_ptr<arrow::RecordBatch> && batch);\n\n    TableRecordBatch(TableRecordBatch &&);\n    TableRecordBatch & operator=(TableRecordBatch &&);\n    TableRecordBatch(TableRecordBatch const &);\n    TableRecordBatch & operator=(TableRecordBatch const &);\n    ~TableRecordBatch();\n\n    std::size_t num_rows() const;\n\n    std::shared_ptr<arrow::RecordBatch> const & batch() const { return m_batch; }\n\nprivate:\n    std::shared_ptr<arrow::RecordBatch> m_batch;\n};\n\nclass POD5_FORMAT_EXPORT TableReader {\npublic:\n    TableReader(\n        std::shared_ptr<void> && input_source,\n        std::shared_ptr<arrow::ipc::RecordBatchFileReader> && reader,\n        SchemaMetadataDescription && schema_metadata,\n        arrow::MemoryPool * pool);\n    TableReader(TableReader &&);\n    TableReader & operator=(TableReader &&);\n    TableReader(TableReader const &) = delete;\n    TableReader & operator=(TableReader const &) = delete;\n    ~TableReader();\n\n    SchemaMetadataDescription const & schema_metadata() const { return m_schema_metadata; }\n\n    std::size_t num_record_batches() const;\n\n    Result<int64_t> CountRows() const;\n\n    Result<std::shared_ptr<arrow::RecordBatch>> ReadRecordBatch(int i) const;\n\nprivate:\n    std::shared_ptr<void> m_input_source;\n    std::shared_ptr<arrow::ipc::RecordBatchFileReader> m_reader;\n    SchemaMetadataDescription m_schema_metadata;\n};\n\n// Same as RecordBatchFileReader::ReadRecordBatch() but validates the contents.\nResult<std::shared_ptr<arrow::RecordBatch>> ReadRecordBatchAndValidate(\n    arrow::ipc::RecordBatchFileReader & reader,\n    int i);\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/thread_pool.cpp",
    "content": "#include \"pod5_format/thread_pool.h\"\n\n#include <atomic>\n#include <cassert>\n#include <condition_variable>\n#include <deque>\n#include <mutex>\n#include <optional>\n#include <stdexcept>\n#include <thread>\n#include <vector>\n\nnamespace pod5 {\n\nnamespace {\n\nclass ThreadPoolImpl : public ThreadPool, public std::enable_shared_from_this<ThreadPoolImpl> {\npublic:\n    ThreadPoolImpl(std::size_t worker_count)\n    {\n        assert(worker_count > 0);\n        for (std::size_t i = 0; i < std::max<std::size_t>(1, worker_count); ++i) {\n            m_threads.emplace_back([&] { run_thread(); });\n        }\n    }\n\n    ~ThreadPoolImpl() { stop_and_drain(); }\n\n    void run_thread()\n    {\n        bool keep_alive = true;\n        std::optional<WorkItem> work;\n\n        while (keep_alive) {\n            {\n                std::unique_lock<std::mutex> lock{m_work_mutex};\n\n                if (work) {\n                    if (work->strand_id != NO_STRAND) {\n                        m_busy_strands[work->strand_id] = false;\n                    }\n                    work = std::nullopt;\n                }\n\n                // find the first piece of work whose strand isn't already busy\n                for (auto it = m_work.begin(); it != m_work.end(); ++it) {\n                    if (it->strand_id == NO_STRAND || !m_busy_strands.at(it->strand_id)) {\n                        if (it->strand_id != NO_STRAND) {\n                            m_busy_strands[it->strand_id] = true;\n                        }\n                        work = std::move(*it);\n                        m_work.erase(it);\n                        break;\n                    }\n                }\n\n                if (!work) {\n                    if (m_keep_alive) {\n                        m_work_ready.wait(lock);\n                        keep_alive = m_keep_alive || !m_work.empty();\n                    } else {\n                        // If there wasn't any work for us to pick up, any remaining work must be\n                        // for strands with running tasks (in which case the workers handling those\n                        // tasks will pick them up. This will work because once a task finishes, the\n                        // worker will check for work *before* checking m_keep_alive. Thus it's safe\n                        // for *this* worker to exit.\n                        keep_alive = false;\n                    }\n                    continue;\n                }\n            }\n            assert(work);\n\n            if (work->callback) {\n                work->callback();\n            }\n        }\n    }\n\n    void post(std::function<void()> callback) override\n    {\n        {\n            std::lock_guard<std::mutex> l{m_work_mutex};\n            if (!m_keep_alive) {\n                throw std::logic_error{\"ThreadPool: post() called after stop_and_drain()\"};\n            }\n            m_work.emplace_back(WorkItem{std::move(callback), NO_STRAND});\n        }\n\n        m_work_ready.notify_one();\n    }\n\n    void post(std::function<void()> callback, uint64_t const strand_id)\n    {\n        assert(strand_id != NO_STRAND);\n\n        std::lock_guard<std::mutex> l{m_work_mutex};\n        if (!m_keep_alive) {\n            throw std::logic_error{\"ThreadPool: post() called after stop_and_drain()\"};\n        }\n        if (m_busy_strands.size() <= strand_id) {\n            m_busy_strands.resize(strand_id + 1);\n        }\n        m_work.emplace_back(WorkItem{std::move(callback), strand_id});\n\n        // only send a wakeup if the strand isn't already busy - it's generally more efficient to do\n        // this outside the lock, but the conditional makes that hard to reason about\n        if (!m_busy_strands.at(strand_id)) {\n            m_work_ready.notify_one();\n        }\n    }\n\n    void stop_and_drain() override\n    {\n        {\n            std::lock_guard<std::mutex> lock{m_work_mutex};\n            m_keep_alive = false;\n        }\n        m_work_ready.notify_all();\n        for (auto & thread : m_threads) {\n            if (thread.joinable()) {\n                thread.join();\n            }\n        }\n\n        assert(m_work.empty());\n    }\n\n    void wait_for_drain() override\n    {\n        auto const drained = [&]() {\n            std::lock_guard<std::mutex> lock{m_work_mutex};\n            return m_work.empty();\n        };\n        while (!drained()) {\n            m_work_ready.notify_all();\n            std::this_thread::sleep_for(std::chrono::milliseconds{10});\n        }\n    }\n\n    std::shared_ptr<ThreadPoolStrand> create_strand() override;\n\nprivate:\n    struct WorkItem {\n        std::function<void()> callback;\n        uint64_t strand_id;\n\n        explicit operator bool() const { return !!callback; }\n    };\n\n    static constexpr uint64_t NO_STRAND = UINT64_MAX;\n\n    std::mutex m_work_mutex;\n    bool m_keep_alive{true};\n    std::condition_variable m_work_ready;\n    std::deque<WorkItem> m_work;\n    std::vector<bool> m_busy_strands;\n\n    std::atomic<uint64_t> m_next_strand_id{0};\n    std::vector<std::thread> m_threads;\n};\n\nclass StrandImpl : public ThreadPoolStrand {\npublic:\n    StrandImpl(std::shared_ptr<ThreadPoolImpl> pool, uint64_t const strand_id)\n    : m_pool(std::move(pool))\n    , m_strand_id(strand_id)\n    {\n    }\n\n    void post(std::function<void()> callback) override\n    {\n        m_pool->post(std::move(callback), m_strand_id);\n    }\n\n    std::shared_ptr<ThreadPoolImpl> m_pool;\n    uint64_t m_strand_id;\n};\n\nstd::shared_ptr<ThreadPoolStrand> ThreadPoolImpl::create_strand()\n{\n    uint64_t strand_id;\n    {\n        std::lock_guard<std::mutex> l{m_work_mutex};\n        if (!m_keep_alive) {\n            throw std::logic_error{\"ThreadPool: create_strand() called after stop_and_drain()\"};\n        }\n        strand_id = m_next_strand_id++;\n    }\n    return std::make_shared<StrandImpl>(shared_from_this(), strand_id);\n}\n}  // namespace\n\nstd::shared_ptr<ThreadPool> make_thread_pool(std::size_t worker_threads)\n{\n    return std::make_shared<ThreadPoolImpl>(worker_threads);\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/thread_pool.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n\n#include <functional>\n#include <memory>\n\nnamespace pod5 {\n\nclass POD5_FORMAT_EXPORT ThreadPoolStrand {\npublic:\n    virtual ~ThreadPoolStrand() = default;\n    virtual void post(std::function<void()> callback) = 0;\n};\n\nclass POD5_FORMAT_EXPORT ThreadPool {\npublic:\n    virtual ~ThreadPool() = default;\n    virtual std::shared_ptr<ThreadPoolStrand> create_strand() = 0;\n    virtual void post(std::function<void()> callback) = 0;\n    /// Stops the thread pool and drains all active work.\n    ///\n    /// Further calls to create_strand() or post() (including on an existing strand created from\n    /// this pool) will throw.\n    virtual void stop_and_drain() = 0;\n\n    /// Waits for the threads to process all posted work.\n    virtual void wait_for_drain() = 0;\n};\n\nPOD5_FORMAT_EXPORT std::shared_ptr<ThreadPool> make_thread_pool(std::size_t worker_threads);\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/tuple_utils.h",
    "content": "#pragma once\n\n#include <tuple>\n#include <utility>\n\nnamespace pod5 { namespace detail {\n\ntemplate <typename T, typename F, int... Is>\nvoid for_each(T && t, F f, std::integer_sequence<int, Is...>)\n{\n    auto l = {(f(std::get<Is>(t), Is), 0)...};\n    (void)l;\n}\n\ntemplate <typename... Ts, typename F>\nvoid for_each_in_tuple(std::tuple<Ts...> & t, F f)\n{\n    detail::for_each(t, f, std::make_integer_sequence<int, sizeof...(Ts)>());\n}\n\ntemplate <typename T1, typename T2, typename F, int... Is>\nvoid for_each_zipped(T1 && t1, T2 && t2, F f, std::integer_sequence<int, Is...>)\n{\n    auto l = {(f(std::get<Is>(t1), std::get<Is>(t2), Is), 0)...};\n    (void)l;\n}\n\ntemplate <typename T1, typename T2, typename F>\nvoid for_each_in_tuple_zipped(T1 & t1, T2 & t2, F f)\n{\n    static_assert(\n        std::tuple_size<T1>::value == std::tuple_size<T2>::value, \"Tuples must be same size\");\n    detail::for_each_zipped(\n        t1, t2, f, std::make_integer_sequence<int, std::tuple_size<T1>::value>());\n}\n\n}}  // namespace pod5::detail\n"
  },
  {
    "path": "c++/pod5_format/types.cpp",
    "content": "#include \"pod5_format/types.h\"\n\n#include <arrow/array/array_binary.h>\n#include <arrow/array/builder_binary.h>\n#include <arrow/util/logging.h>\n\n#include <mutex>\n\nnamespace pod5 {\n\nUuid const * UuidArray::raw_values() const\n{\n    auto const & array = static_cast<arrow::FixedSizeBinaryArray const &>(*storage_);\n    return reinterpret_cast<Uuid const *>(array.GetValue(0));\n}\n\nUuid UuidArray::Value(int64_t i) const\n{\n    auto const & array = static_cast<arrow::FixedSizeBinaryArray const &>(*storage_);\n    return *reinterpret_cast<Uuid const *>(array.GetValue(i));\n}\n\nbool UuidType::ExtensionEquals(ExtensionType const & other) const\n{\n    // no parameters to consider\n    return other.extension_name() == extension_name();\n}\n\nstd::shared_ptr<arrow::Array> UuidType::MakeArray(std::shared_ptr<arrow::ArrayData> data) const\n{\n    DCHECK_EQ(data->type->id(), arrow::Type::EXTENSION);\n    DCHECK_EQ(\n        static_cast<arrow::ExtensionType const &>(*data->type).extension_name(), extension_name());\n    return std::make_shared<UuidArray>(data);\n}\n\nstd::string UuidType::Serialize() const { return \"\"; }\n\narrow::Result<std::shared_ptr<arrow::DataType>> UuidType::Deserialize(\n    std::shared_ptr<arrow::DataType> storage_type,\n    std::string const & serialized_data) const\n{\n    if (serialized_data != \"\") {\n        return arrow::Status::Invalid(\"Unexpected type metadata: '\", serialized_data, \"'\");\n    }\n    if (!storage_type->Equals(*arrow::fixed_size_binary(16))) {\n        return arrow::Status::Invalid(\n            \"Incorrect storage for UuidType: '\", storage_type->ToString(), \"'\");\n    }\n    return std::make_shared<UuidType>();\n}\n\ngsl::span<std::uint8_t const> VbzSignalArray::Value(int64_t i) const\n{\n    auto const & array = static_cast<arrow::LargeBinaryArray const &>(*storage_);\n\n    arrow::LargeBinaryArray::offset_type value_length = 0;\n    auto value_ptr = array.GetValue(i, &value_length);\n    return gsl::make_span(value_ptr, value_length);\n}\n\nstd::shared_ptr<arrow::Buffer> VbzSignalArray::ValueAsBuffer(int64_t i) const\n{\n    auto const & array = static_cast<arrow::LargeBinaryArray const &>(*storage_);\n\n    auto offset = array.value_offset(i);\n    auto length = array.value_length(i);\n    auto const value_data = array.value_data();\n\n    return arrow::SliceBuffer(value_data, offset, length);\n}\n\nbool VbzSignalType::ExtensionEquals(ExtensionType const & other) const\n{\n    // no parameters to consider\n    return other.extension_name() == extension_name();\n}\n\nstd::shared_ptr<arrow::Array> VbzSignalType::MakeArray(std::shared_ptr<arrow::ArrayData> data) const\n{\n    DCHECK_EQ(data->type->id(), arrow::Type::EXTENSION);\n    DCHECK_EQ(\n        static_cast<arrow::ExtensionType const &>(*data->type).extension_name(), extension_name());\n    return std::make_shared<VbzSignalArray>(data);\n}\n\nstd::string VbzSignalType::Serialize() const { return \"\"; }\n\narrow::Result<std::shared_ptr<arrow::DataType>> VbzSignalType::Deserialize(\n    std::shared_ptr<arrow::DataType> storage_type,\n    std::string const & serialized_data) const\n{\n    if (serialized_data != \"\") {\n        return arrow::Status::Invalid(\"Unexpected type metadata: '\", serialized_data, \"'\");\n    }\n    if (!storage_type->Equals(*arrow::large_binary())) {\n        return arrow::Status::Invalid(\n            \"Incorrect storage for VbzSignalType: '\", storage_type->ToString(), \"'\");\n    }\n    return std::make_shared<VbzSignalType>();\n}\n\nstd::unique_ptr<arrow::FixedSizeBinaryBuilder> make_read_id_builder(arrow::MemoryPool * pool)\n{\n    auto const & uuid_type = uuid();\n    assert(uuid_type->id() == arrow::Type::EXTENSION);\n    auto result = std::make_unique<arrow::FixedSizeBinaryBuilder>(uuid_type->storage_type(), pool);\n    assert(result->byte_width() == 16);\n    return result;\n}\n\nstd::shared_ptr<VbzSignalType> const & vbz_signal()\n{\n    static auto vbz_signal = std::make_shared<VbzSignalType>();\n    return vbz_signal;\n}\n\nstd::shared_ptr<UuidType> const & uuid()\n{\n    static auto uuid = std::make_shared<UuidType>();\n    return uuid;\n}\n\nnamespace {\n\nstd::mutex & get_pod5_register_mutex()\n{\n    // Heap allocated so that it's safe for user code to call during static init\n    // and destruction, not that they should.\n    static std::mutex & m = *new std::mutex{};\n    return m;\n}\n\nstd::size_t s_pod5_register_count(0);\n\n}  // namespace\n\npod5::Status register_extension_types()\n{\n    std::lock_guard lock(get_pod5_register_mutex());\n    if (++s_pod5_register_count == 1) {\n        ARROW_RETURN_NOT_OK(arrow::RegisterExtensionType(uuid()));\n        ARROW_RETURN_NOT_OK(arrow::RegisterExtensionType(vbz_signal()));\n    }\n    return pod5::Status::OK();\n}\n\npod5::Status unregister_extension_types()\n{\n    std::lock_guard lock(get_pod5_register_mutex());\n    auto register_count = --s_pod5_register_count;\n    if (register_count == 0) {\n        if (arrow::GetExtensionType(\"minknow.uuid\")) {\n            ARROW_RETURN_NOT_OK(arrow::UnregisterExtensionType(\"minknow.uuid\"));\n        }\n        if (arrow::GetExtensionType(\"minknow.vbz\")) {\n            ARROW_RETURN_NOT_OK(arrow::UnregisterExtensionType(\"minknow.vbz\"));\n        }\n    }\n    return pod5::Status::OK();\n}\n\nbool check_extension_types_registered()\n{\n    std::lock_guard lock(get_pod5_register_mutex());\n    return s_pod5_register_count > 0;\n}\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/types.h",
    "content": "#pragma once\n\n#include \"pod5_format/pod5_format_export.h\"\n#include \"pod5_format/result.h\"\n#include \"pod5_format/uuid.h\"\n\n#include <arrow/extension_type.h>\n#include <arrow/stl_iterator.h>\n#include <gsl/gsl-lite.hpp>\n\nnamespace pod5 {\n\nclass POD5_FORMAT_EXPORT UuidArray : public arrow::ExtensionArray {\npublic:\n    using IteratorType = arrow::stl::ArrayIterator<UuidArray>;\n\n    using ExtensionArray::ExtensionArray;\n\n    Uuid const * raw_values() const;\n\n    Uuid Value(int64_t i) const;\n\n    // this isn't actually a view - it copies the data - but\n    // (a) it's only 16 bytes, which is what a view (pointer + size) would require anyway\n    // (b) arrow::std::ArrayIterator hard-codes the name of this method (even though it is supposed\n    //     to be configurable via the ValueAccessor template parameter)\n    Uuid GetView(int64_t i) const { return Value(i); }\n\n    std::optional<Uuid> operator[](int64_t i) const { return *IteratorType(*this, i); }\n\n    IteratorType begin() const { return IteratorType(*this); }\n\n    IteratorType end() const { return IteratorType(*this, length()); }\n};\n\nclass POD5_FORMAT_EXPORT UuidType : public arrow::ExtensionType {\npublic:\n    UuidType() : ExtensionType(arrow::fixed_size_binary(16)) {}\n\n    std::string extension_name() const override { return \"minknow.uuid\"; }\n\n    bool ExtensionEquals(ExtensionType const & other) const override;\n    std::shared_ptr<arrow::Array> MakeArray(std::shared_ptr<arrow::ArrayData> data) const override;\n    std::string Serialize() const override;\n    arrow::Result<std::shared_ptr<arrow::DataType>> Deserialize(\n        std::shared_ptr<arrow::DataType> storage_type,\n        std::string const & serialized_data) const override;\n};\n\nclass POD5_FORMAT_EXPORT VbzSignalArray : public arrow::ExtensionArray {\npublic:\n    using IteratorType = arrow::stl::ArrayIterator<VbzSignalArray>;\n\n    gsl::span<std::uint8_t const> Value(int64_t i) const;\n    std::shared_ptr<arrow::Buffer> ValueAsBuffer(int64_t i) const;\n\n    using ExtensionArray::ExtensionArray;\n};\n\nclass POD5_FORMAT_EXPORT VbzSignalType : public arrow::ExtensionType {\npublic:\n    VbzSignalType() : ExtensionType(arrow::large_binary()) {}\n\n    std::string extension_name() const override { return \"minknow.vbz\"; }\n\n    bool ExtensionEquals(ExtensionType const & other) const override;\n    std::shared_ptr<arrow::Array> MakeArray(std::shared_ptr<arrow::ArrayData> data) const override;\n    std::string Serialize() const override;\n    arrow::Result<std::shared_ptr<arrow::DataType>> Deserialize(\n        std::shared_ptr<arrow::DataType> storage_type,\n        std::string const & serialized_data) const override;\n};\n\nstd::unique_ptr<arrow::FixedSizeBinaryBuilder> make_read_id_builder(arrow::MemoryPool * pool);\n\nstd::shared_ptr<VbzSignalType> const & vbz_signal();\nstd::shared_ptr<UuidType> const & uuid();\n\n/// \\brief Register all required extension types.\nPOD5_FORMAT_EXPORT pod5::Status register_extension_types();\n\n/// \\brief Unregister all required extension types.\nPOD5_FORMAT_EXPORT pod5::Status unregister_extension_types();\n\n/// \\brief Returns true iff the required extension types are registered.\n/// \\details The caller can expect the extension types to be registered if the number of calls to\n/// `register_extension_types` exceeds the number of calls to `unregister_extension_types`.\nbool check_extension_types_registered();\n\n}  // namespace pod5\n"
  },
  {
    "path": "c++/pod5_format/uuid.h",
    "content": "#pragma once\n\n// This file contains code from https://github.com/mariusbancila/stduuid/ which has the following\n// license:\n//\n//   MIT License\n//\n//   Copyright (c) 2017\n//\n//   Permission is hereby granted, free of charge, to any person obtaining a copy\n//   of this software and associated documentation files (the \"Software\"), to deal\n//   in the Software without restriction, including without limitation the rights\n//   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n//   copies of the Software, and to permit persons to whom the Software is\n//   furnished to do so, subject to the following conditions:\n//\n//   The above copyright notice and this permission notice shall be included in all\n//   copies or substantial portions of the Software.\n//\n//   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n//   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n//   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n//   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n//   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n//   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n//   SOFTWARE.\n\n#include <array>\n#include <cstdint>\n#include <iosfwd>\n#include <optional>\n#include <random>\n#include <string>\n#include <string_view>\n\nnamespace pod5 {\n\nnamespace uuid_detail {\n\ntemplate <typename TChar>\n[[nodiscard]] constexpr inline unsigned char hex2char(TChar const ch) noexcept\n{\n    if (ch >= static_cast<TChar>('0') && ch <= static_cast<TChar>('9')) {\n        return static_cast<unsigned char>(ch - static_cast<TChar>('0'));\n    }\n    if (ch >= static_cast<TChar>('a') && ch <= static_cast<TChar>('f')) {\n        return static_cast<unsigned char>(10 + ch - static_cast<TChar>('a'));\n    }\n    if (ch >= static_cast<TChar>('A') && ch <= static_cast<TChar>('F')) {\n        return static_cast<unsigned char>(10 + ch - static_cast<TChar>('A'));\n    }\n    return 0;\n}\n\ntemplate <typename TChar>\n[[nodiscard]] constexpr inline bool is_hex(TChar const ch) noexcept\n{\n    return (ch >= static_cast<TChar>('0') && ch <= static_cast<TChar>('9'))\n           || (ch >= static_cast<TChar>('a') && ch <= static_cast<TChar>('f'))\n           || (ch >= static_cast<TChar>('A') && ch <= static_cast<TChar>('F'));\n}\n\ntemplate <typename TChar>\n[[nodiscard]] constexpr std::basic_string_view<TChar> to_string_view(TChar const * str) noexcept\n{\n    if (str) {\n        return str;\n    }\n    return {};\n}\n\ntemplate <typename StringType>\n[[nodiscard]] constexpr std::\n    basic_string_view<typename StringType::value_type, typename StringType::traits_type>\n    to_string_view(StringType const & str) noexcept\n{\n    return str;\n}\n\ntemplate <typename CharT>\ninline constexpr CharT empty_guid[37] = \"00000000-0000-0000-0000-000000000000\";\n\ntemplate <>\ninline constexpr wchar_t empty_guid<wchar_t>[37] = L\"00000000-0000-0000-0000-000000000000\";\n\ntemplate <typename CharT>\ninline constexpr CharT guid_encoder[17] = \"0123456789abcdef\";\n\ntemplate <>\ninline constexpr wchar_t guid_encoder<wchar_t>[17] = L\"0123456789abcdef\";\n\n}  // namespace uuid_detail\n\n// Forward declare uuid & to_string so that we can declare to_string as a friend later.\nclass Uuid;\ntemplate <\n    class CharT = char,\n    class Traits = std::char_traits<CharT>,\n    class Allocator = std::allocator<CharT>>\nstd::basic_string<CharT, Traits, Allocator> to_string(Uuid const & id);\n\n/// A representation of a Universally Unique IDentifier.\n///\n/// This code implements part of RFC 4122. It does not aim to be a complete implementation of UUIDs,\n/// but provides enough for the uses in the POD5 library.\nclass Uuid {\npublic:\n    using value_type = uint8_t;\n\n    constexpr Uuid() noexcept = default;\n\n    Uuid(value_type const (&arr)[16]) noexcept\n    {\n        std::copy(std::cbegin(arr), std::cend(arr), std::begin(m_data));\n    }\n\n    constexpr Uuid(std::array<value_type, 16> const & arr) noexcept : m_data{arr} {}\n\n    template <typename ForwardIterator>\n    explicit Uuid(ForwardIterator first, ForwardIterator last)\n    {\n        if (std::distance(first, last) == 16) {\n            std::copy(first, last, std::begin(m_data));\n        }\n    }\n\n    [[nodiscard]] constexpr bool is_nil() const noexcept\n    {\n        for (size_t i = 0; i < m_data.size(); ++i) {\n            if (m_data[i] != 0) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    void swap(Uuid & other) noexcept { m_data.swap(other.m_data); }\n\n    uint8_t const * data() const noexcept { return m_data.data(); }\n\n    size_t size() const noexcept { return m_data.size(); }\n\n    void to_c_array(value_type (&arr)[16]) const noexcept\n    {\n        std::copy(std::cbegin(m_data), std::cend(m_data), std::begin(arr));\n    }\n\n    // Note: uustr must be at least 36 characters\n    template <typename CharT>\n    void write_to(CharT * uustr) const noexcept\n    {\n        for (size_t i = 0, index = 0; i < 36; ++i) {\n            if (i == 8 || i == 13 || i == 18 || i == 23) {\n                uustr[i] = uuid_detail::empty_guid<CharT>[i];\n                continue;\n            }\n            uustr[i] = uuid_detail::guid_encoder<CharT>[m_data[index] >> 4 & 0x0f];\n            uustr[++i] = uuid_detail::guid_encoder<CharT>[m_data[index] & 0x0f];\n            index++;\n        }\n    }\n\n    template <typename StringType>\n    [[nodiscard]] constexpr static std::optional<Uuid> from_string(\n        StringType const & in_str) noexcept\n    {\n        auto str = uuid_detail::to_string_view(in_str);\n        bool firstDigit = true;\n        size_t hasBraces = 0;\n        size_t index = 0;\n\n        std::array<uint8_t, 16> data{{0}};\n\n        if (str.empty()) {\n            return {};\n        }\n\n        if (str.front() == '{') {\n            hasBraces = 1;\n        }\n        if (hasBraces && str.back() != '}') {\n            return {};\n        }\n\n        for (size_t i = hasBraces; i < str.size() - hasBraces; ++i) {\n            if (str[i] == '-') {\n                continue;\n            }\n\n            if (index >= 16 || !uuid_detail::is_hex(str[i])) {\n                return {};\n            }\n\n            if (firstDigit) {\n                data[index] = static_cast<uint8_t>(uuid_detail::hex2char(str[i]) << 4);\n                firstDigit = false;\n            } else {\n                data[index] = static_cast<uint8_t>(data[index] | uuid_detail::hex2char(str[i]));\n                index++;\n                firstDigit = true;\n            }\n        }\n\n        if (index < 16) {\n            return {};\n        }\n\n        return Uuid{data};\n    }\n\nprivate:\n    std::array<value_type, 16> m_data{{0}};\n\n    friend bool operator==(Uuid const & lhs, Uuid const & rhs) noexcept;\n    friend bool operator<(Uuid const & lhs, Uuid const & rhs) noexcept;\n\n    template <class Elem, class Traits>\n    friend std::basic_ostream<Elem, Traits> & operator<<(\n        std::basic_ostream<Elem, Traits> & s,\n        Uuid const & id);\n\n    template <class CharT, class Traits, class Allocator>\n    friend std::basic_string<CharT, Traits, Allocator> to_string(Uuid const & id);\n\n    friend std::hash<Uuid>;\n};\n\n[[nodiscard]] inline bool operator==(Uuid const & lhs, Uuid const & rhs) noexcept\n{\n    return lhs.m_data == rhs.m_data;\n}\n\n[[nodiscard]] inline bool operator!=(Uuid const & lhs, Uuid const & rhs) noexcept\n{\n    return !(lhs == rhs);\n}\n\n[[nodiscard]] inline bool operator<(Uuid const & lhs, Uuid const & rhs) noexcept\n{\n    return lhs.m_data < rhs.m_data;\n}\n\ntemplate <class CharT, class Traits, class Allocator>\n[[nodiscard]] inline std::basic_string<CharT, Traits, Allocator> to_string(Uuid const & id)\n{\n    std::basic_string<CharT, Traits, Allocator> uustr{uuid_detail::empty_guid<CharT>};\n    id.write_to(uustr.data());\n    return uustr;\n}\n\ntemplate <class Elem, class Traits>\nstd::basic_ostream<Elem, Traits> & operator<<(std::basic_ostream<Elem, Traits> & s, Uuid const & id)\n{\n    s << to_string(id);\n    return s;\n}\n\ninline void swap(Uuid & lhs, Uuid & rhs) noexcept { lhs.swap(rhs); }\n\ntemplate <typename UniformRandomNumberGenerator>\nclass BasicUuidRandomGenerator {\npublic:\n    using engine_type = UniformRandomNumberGenerator;\n\n    explicit BasicUuidRandomGenerator(engine_type & gen) : generator(&gen) {}\n\n    explicit BasicUuidRandomGenerator(engine_type * gen) : generator(gen) {}\n\n    [[nodiscard]] Uuid operator()()\n    {\n        alignas(uint32_t) uint8_t bytes[16];\n        for (int i = 0; i < 16; i += 4) {\n            *reinterpret_cast<uint32_t *>(bytes + i) = distribution(*generator);\n        }\n\n        // variant must be 10xxxxxx\n        bytes[8] &= 0xBF;\n        bytes[8] |= 0x80;\n\n        // version must be 0100xxxx\n        bytes[6] &= 0x4F;\n        bytes[6] |= 0x40;\n\n        return Uuid{std::begin(bytes), std::end(bytes)};\n    }\n\nprivate:\n    std::uniform_int_distribution<uint32_t> distribution;\n    UniformRandomNumberGenerator * generator;\n};\n\nusing UuidRandomGenerator = BasicUuidRandomGenerator<std::mt19937>;\n\n}  // namespace pod5\n\nnamespace std {\ntemplate <>\nstruct hash<pod5::Uuid> {\n    using argument_type = pod5::Uuid;\n    using result_type = std::size_t;\n\n    [[nodiscard]] result_type operator()(argument_type const & uuid) const\n    {\n        uint64_t l = static_cast<uint64_t>(uuid.m_data[0]) << 56\n                     | static_cast<uint64_t>(uuid.m_data[1]) << 48\n                     | static_cast<uint64_t>(uuid.m_data[2]) << 40\n                     | static_cast<uint64_t>(uuid.m_data[3]) << 32\n                     | static_cast<uint64_t>(uuid.m_data[4]) << 24\n                     | static_cast<uint64_t>(uuid.m_data[5]) << 16\n                     | static_cast<uint64_t>(uuid.m_data[6]) << 8\n                     | static_cast<uint64_t>(uuid.m_data[7]);\n        uint64_t h = static_cast<uint64_t>(uuid.m_data[8]) << 56\n                     | static_cast<uint64_t>(uuid.m_data[9]) << 48\n                     | static_cast<uint64_t>(uuid.m_data[10]) << 40\n                     | static_cast<uint64_t>(uuid.m_data[11]) << 32\n                     | static_cast<uint64_t>(uuid.m_data[12]) << 24\n                     | static_cast<uint64_t>(uuid.m_data[13]) << 16\n                     | static_cast<uint64_t>(uuid.m_data[14]) << 8\n                     | static_cast<uint64_t>(uuid.m_data[15]);\n\n        if constexpr (sizeof(result_type) > 4) {\n            return result_type(l ^ h);\n        } else {\n            uint64_t hash64 = l ^ h;\n            return result_type(uint32_t(hash64 >> 32) ^ uint32_t(hash64));\n        }\n    }\n};\n}  // namespace std\n"
  },
  {
    "path": "c++/pod5_format/version.h.in",
    "content": "#pragma once\n\n#include <string>\n\nnamespace pod5 {\n\nstd::uint16_t const Pod5MajorVersion = @POD5_VERSION_MAJOR@;\nstd::uint16_t const Pod5MinorVersion = @POD5_VERSION_MINOR@;\nstd::uint16_t const Pod5RevVersion = @POD5_VERSION_REV@;\n\nstd::string const Pod5Version = \"@POD5_NUMERIC_VERSION@\";\n\n}\n"
  },
  {
    "path": "c++/pod5_format_pybind/CMakeLists.txt",
    "content": "\npybind11_add_module(pod5_format_pybind\n    api.h\n    bindings.cpp\n    utils.h\n    subset.cpp\n    subset.h\n\n    repack/repack_functions.h\n    repack/repack_states.h\n    repack/repack_utils.h\n    repack/repack_output.cpp\n    repack/repack_output.h\n    repack/repacker.cpp\n    repack/repacker.h\n)\n\ntarget_link_libraries(pod5_format_pybind\n    PRIVATE\n        pod5_format\n)\n\nif (NOT MSVC)\n    target_compile_options(pod5_format_pybind PRIVATE ${pod5_warning_options})\nendif()\n\nset_target_properties(pod5_format_pybind\n    PROPERTIES\n        POSITION_INDEPENDENT_CODE 1\n        CXX_STANDARD 20\n)\n\n# Non-conan license files to copy.\nset(pod5_cxx_licenses_src\n    \"${CMAKE_SOURCE_DIR}/LICENSE.md\"\n    \"${CMAKE_SOURCE_DIR}/third_party/licenses/gsl-lite.txt\"\n    \"${CMAKE_SOURCE_DIR}/third_party/pybind11/LICENSE\"\n)\n# Destination name for the above files.\nset(pod5_cxx_licenses_dst\n    \"LICENSE.md\"\n    \"gsl-lite.txt\"\n    \"pybind11.txt\"\n)\n\nset(python_project_root \"${CMAKE_SOURCE_DIR}/python/lib_pod5/\")\n\nconfigure_file(\n    ${CMAKE_CURRENT_SOURCE_DIR}/_version.py.in\n    ${python_project_root}/src/lib_pod5/_version.py\n)\n\nset(wheel_output_stub \"${CMAKE_CURRENT_BINARY_DIR}/wheel.touch\")\n\nset(wheel_output_dir \"${CMAKE_CURRENT_BINARY_DIR}/wheel_${POD5_FULL_VERSION}\")\nfile(MAKE_DIRECTORY ${wheel_output_dir})\n\nadd_custom_command(\n    OUTPUT \"${wheel_output_stub}\"\n    COMMAND ${CMAKE_COMMAND}\n    ARGS\n        -D \"PYTHON_EXECUTABLE=${Python_EXECUTABLE}\"\n        -D \"PYTHON_PROJECT_DIR=${python_project_root}\"\n        -D \"PYBIND_INPUT_LIB=$<TARGET_FILE:pod5_format_pybind>\"\n        -D \"WHEEL_OUTPUT_DIR=${wheel_output_dir}\"\n        -D \"POD5_CONAN_LICENSES=${CMAKE_BINARY_DIR}/pod5_conan_licenses\"\n        -D \"POD5_CXX_LICENSES_SRC=${pod5_cxx_licenses_src}\"\n        -D \"POD5_CXX_LICENSES_DST=${pod5_cxx_licenses_dst}\"\n        -P \"${CMAKE_CURRENT_SOURCE_DIR}/build_wheel.cmake\"\n    DEPENDS\n        pod5_format_pybind\n    VERBATIM\n)\n\nadd_custom_target(lib_pod5_python_wheel ALL\n    SOURCES\n        build_wheel.cmake\n    DEPENDS\n        \"${wheel_output_stub}\"\n)\n\ninstall(\n    DIRECTORY \"${wheel_output_dir}/\"\n    DESTINATION \".\"\n    COMPONENT wheel\n    FILES_MATCHING PATTERN \"lib_pod5*.whl\"\n)\n"
  },
  {
    "path": "c++/pod5_format_pybind/_version.py.in",
    "content": "# This file is auto generated by cmake during compilation\n__version__ = version = \"@POD5_FULL_VERSION@\"\n__version_tuple__ = version_tuple = (@POD5_VERSION_MAJOR@, @POD5_VERSION_MINOR@, @POD5_VERSION_REV@)\n"
  },
  {
    "path": "c++/pod5_format_pybind/api.h",
    "content": "#pragma once\n\n#include \"pod5_format/async_signal_loader.h\"\n#include \"pod5_format/c_api.h\"\n#include \"pod5_format/file_reader.h\"\n#include \"pod5_format/file_updater.h\"\n#include \"pod5_format/file_writer.h\"\n#include \"pod5_format/read_table_reader.h\"\n#include \"pod5_format/signal_compression.h\"\n#include \"pod5_format/signal_table_reader.h\"\n#include \"pod5_format/thread_pool.h\"\n#include \"pod5_format/uuid.h\"\n#include \"utils.h\"\n\n#include <arrow/memory_pool.h>\n#include <pybind11/numpy.h>\n#include <pybind11/pybind11.h>\n#include <pybind11/stl.h>\n\nnamespace py = pybind11;\n\ninline std::shared_ptr<pod5::FileWriter> create_file(\n    char const * path,\n    std::string const & writer_name,\n    pod5::FileWriterOptions const * options)\n{\n    pod5::FileWriterOptions dummy;\n    POD5_PYTHON_ASSIGN_OR_RAISE(\n        auto writer,\n        pod5::create_file_writer(\n            path, writer_name, options ? *options : pod5::FileWriterOptions{}));\n    return writer;\n}\n\ninline pod5::RecoveryDetails recover_file(\n    char const * src_filename,\n    char const * dest_filename,\n    pod5::RecoverFileOptions const * const options)\n{\n    POD5_PYTHON_ASSIGN_OR_RAISE(\n        pod5::RecoveryDetails details,\n        pod5::recover_file(\n            src_filename, dest_filename, options ? *options : pod5::RecoverFileOptions{}));\n    return details;\n}\n\nclass Pod5SignalCacheBatch {\npublic:\n    Pod5SignalCacheBatch(\n        pod5::AsyncSignalLoader::SamplesMode samples_mode,\n        pod5::CachedBatchSignalData && cached_data)\n    : m_samples_mode(samples_mode)\n    , m_cached_data(std::move(cached_data))\n    {\n    }\n\n    py::array_t<std::uint64_t> sample_count() const\n    {\n        return py::array_t<std::uint64_t>(\n            m_cached_data.sample_count().size(), m_cached_data.sample_count().data());\n    }\n\n    py::list samples() const\n    {\n        py::list py_samples;\n        if (m_samples_mode != pod5::AsyncSignalLoader::SamplesMode::Samples) {\n            return py_samples;\n        }\n        for (auto const & row_samples : m_cached_data.samples()) {\n            py_samples.append(\n                py::array_t<std::int16_t>(\n                    {row_samples.size()}, {sizeof(std::int16_t)}, row_samples.data()));\n        }\n\n        return py_samples;\n    }\n\n    std::uint32_t batch_index() const { return m_cached_data.batch_index(); }\n\nprivate:\n    pod5::AsyncSignalLoader::SamplesMode m_samples_mode;\n    pod5::CachedBatchSignalData m_cached_data;\n};\n\nclass Pod5AsyncSignalLoader {\npublic:\n    // Make an async loader for all reads in the file\n    Pod5AsyncSignalLoader(\n        std::shared_ptr<pod5::FileReader> const & reader,\n        pod5::AsyncSignalLoader::SamplesMode samples_mode,\n        std::size_t worker_count = std::thread::hardware_concurrency(),\n        std::size_t max_pending_batches = 10)\n    : m_samples_mode(samples_mode)\n    , m_batch_counts_ref({})\n    , m_batch_rows_ref({})\n    , m_async_loader(reader, samples_mode, {}, {}, worker_count, max_pending_batches)\n    {\n    }\n\n    // Make an async loader for specific batches\n    Pod5AsyncSignalLoader(\n        std::shared_ptr<pod5::FileReader> const & reader,\n        pod5::AsyncSignalLoader::SamplesMode samples_mode,\n        py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> && batches,\n        std::size_t worker_count = std::thread::hardware_concurrency(),\n        std::size_t max_pending_batches = 10)\n    : m_samples_mode(samples_mode)\n    , m_batch_sizes(make_batch_counts(reader, batches))\n    , m_async_loader(\n          reader,\n          samples_mode,\n          gsl::make_span(m_batch_sizes),\n          {},\n          worker_count,\n          max_pending_batches)\n    {\n    }\n\n    // Make an async loader for specific reads in specific batches\n    Pod5AsyncSignalLoader(\n        std::shared_ptr<pod5::FileReader> const & reader,\n        pod5::AsyncSignalLoader::SamplesMode samples_mode,\n        py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> && batch_counts,\n        py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> && batch_rows,\n        std::size_t worker_count = std::thread::hardware_concurrency(),\n        std::size_t max_pending_batches = 10)\n    : m_samples_mode(samples_mode)\n    , m_batch_counts_ref(std::move(batch_counts))\n    , m_batch_rows_ref(std::move(batch_rows))\n    , m_async_loader(\n          reader,\n          samples_mode,\n          gsl::make_span(m_batch_counts_ref.data(), m_batch_counts_ref.size()),\n          gsl::make_span(m_batch_rows_ref.data(), m_batch_rows_ref.size()),\n          worker_count,\n          max_pending_batches)\n    {\n    }\n\n    std::shared_ptr<Pod5SignalCacheBatch> release_next_batch()\n    {\n        auto batch = m_async_loader.release_next_batch();\n        if (!batch.ok()) {\n            throw std::runtime_error(batch.status().ToString());\n        }\n\n        if (!*batch) {\n            assert(m_async_loader.is_finished());\n            throw pybind11::stop_iteration();\n        }\n\n        return std::make_shared<Pod5SignalCacheBatch>(m_samples_mode, std::move(**batch));\n    }\n\n    std::vector<std::uint32_t> make_batch_counts(\n        std::shared_ptr<pod5::FileReader> const & reader,\n        py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> const & batches)\n    {\n        std::vector<std::uint32_t> batch_counts(reader->num_read_record_batches(), 0);\n        for (auto const & batch_idx : gsl::make_span(batches.data(), batches.shape(0))) {\n            auto read_batch = reader->read_read_record_batch(batch_idx);\n            if (!read_batch.ok()) {\n                throw std::runtime_error(\n                    \"Failed to query read batch count: \" + read_batch.status().ToString());\n            }\n\n            batch_counts[batch_idx] = read_batch->num_rows();\n        }\n        return batch_counts;\n    }\n\n    pod5::AsyncSignalLoader::SamplesMode m_samples_mode;\n    std::vector<std::uint32_t> m_batch_sizes;\n    py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> m_batch_counts_ref;\n    py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> m_batch_rows_ref;\n    pod5::AsyncSignalLoader m_async_loader;\n};\n\nstruct Pod5FileReaderPtr {\n    std::shared_ptr<pod5::FileReader> reader = nullptr;\n\n    Pod5FileReaderPtr(std::shared_ptr<pod5::FileReader> && reader_) : reader(std::move(reader_)) {}\n\n    pod5::FileLocation get_file_run_info_table_location() const\n    {\n        return reader->run_info_table_location();\n    }\n\n    pod5::FileLocation get_file_read_table_location() const\n    {\n        return reader->read_table_location();\n    }\n\n    pod5::FileLocation get_file_signal_table_location() const\n    {\n        return reader->signal_table_location();\n    }\n\n    std::string get_file_version_pre_migration() const\n    {\n        return reader->file_version_pre_migration().to_string();\n    }\n\n    void close() { reader = nullptr; }\n\n    std::size_t plan_traversal(\n        py::array_t<std::uint8_t, py::array::c_style | py::array::forcecast> const & read_id_data,\n        py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> & batch_counts,\n        py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> & batch_rows)\n    {\n        auto const read_id_count = read_id_data.shape(0);\n        auto search_input = pod5::ReadIdSearchInput(\n            gsl::make_span(\n                reinterpret_cast<pod5::Uuid const *>(read_id_data.data()), read_id_count));\n\n        POD5_PYTHON_ASSIGN_OR_RAISE(\n            auto find_success_count,\n            reader->search_for_read_ids(\n                search_input,\n                gsl::make_span(batch_counts.mutable_data(), reader->num_read_record_batches()),\n                gsl::make_span(batch_rows.mutable_data(), read_id_count)));\n\n        return find_success_count;\n    }\n\n    std::shared_ptr<Pod5AsyncSignalLoader> batch_get_signal(bool get_samples, bool get_sample_count)\n    {\n        return std::make_shared<Pod5AsyncSignalLoader>(\n            reader,\n            get_samples ? pod5::AsyncSignalLoader::SamplesMode::Samples\n                        : pod5::AsyncSignalLoader::SamplesMode::NoSamples);\n    }\n\n    std::shared_ptr<Pod5AsyncSignalLoader> batch_get_signal_batches(\n        bool get_samples,\n        bool get_sample_count,\n        py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> && batches)\n    {\n        return std::make_shared<Pod5AsyncSignalLoader>(\n            reader,\n            get_samples ? pod5::AsyncSignalLoader::SamplesMode::Samples\n                        : pod5::AsyncSignalLoader::SamplesMode::NoSamples,\n            std::move(batches));\n    }\n\n    std::shared_ptr<Pod5AsyncSignalLoader> batch_get_signal_selection(\n        bool get_samples,\n        bool get_sample_count,\n        py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> && batch_counts,\n        py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> && batch_rows)\n    {\n        return std::make_shared<Pod5AsyncSignalLoader>(\n            reader,\n            get_samples ? pod5::AsyncSignalLoader::SamplesMode::Samples\n                        : pod5::AsyncSignalLoader::SamplesMode::NoSamples,\n            std::move(batch_counts),\n            std::move(batch_rows));\n    }\n};\n\ninline Pod5FileReaderPtr open_file(char const * filename)\n{\n    POD5_PYTHON_ASSIGN_OR_RAISE(auto reader, pod5::open_file_reader(filename, {}));\n    return Pod5FileReaderPtr(std::move(reader));\n}\n\ninline void write_updated_file_to_dest(Pod5FileReaderPtr source, char const * dest_filename)\n{\n    POD5_PYTHON_RETURN_NOT_OK(\n        pod5::update_file(arrow::default_memory_pool(), source.reader, dest_filename));\n}\n\ninline pod5::RunInfoDictionaryIndex FileWriter_add_run_info(\n    pod5::FileWriter & w,\n    std::string & acquisition_id,\n    std::int64_t acquisition_start_time,\n    std::int16_t adc_max,\n    std::int16_t adc_min,\n    std::vector<std::pair<std::string, std::string>> && context_tags,\n    std::string & experiment_name,\n    std::string & flow_cell_id,\n    std::string & flow_cell_product_code,\n    std::string & protocol_name,\n    std::string & protocol_run_id,\n    std::int64_t protocol_start_time,\n    std::string & sample_id,\n    std::uint16_t sample_rate,\n    std::string & sequencing_kit,\n    std::string & sequencer_position,\n    std::string & sequencer_position_type,\n    std::string & software,\n    std::string & system_name,\n    std::string & system_type,\n    std::vector<std::pair<std::string, std::string>> && tracking_id)\n{\n    return throw_on_error(w.add_run_info(\n        {std::move(acquisition_id),\n         acquisition_start_time,\n         adc_max,\n         adc_min,\n         std::move(context_tags),\n         std::move(experiment_name),\n         std::move(flow_cell_id),\n         std::move(flow_cell_product_code),\n         std::move(protocol_name),\n         std::move(protocol_run_id),\n         std::move(protocol_start_time),\n         std::move(sample_id),\n         sample_rate,\n         std::move(sequencing_kit),\n         std::move(sequencer_position),\n         std::move(sequencer_position_type),\n         std::move(software),\n         std::move(system_name),\n         std::move(system_type),\n         std::move(tracking_id)}));\n}\n\ninline pod5::ReadData make_read_data(\n    std::size_t row_id,\n    py::array_t<std::uint8_t, py::array::c_style | py::array::forcecast> const & read_id_data,\n    py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> const & read_numbers,\n    py::array_t<std::uint64_t, py::array::c_style | py::array::forcecast> const & start_samples,\n    py::array_t<std::uint16_t, py::array::c_style | py::array::forcecast> const & channels,\n    py::array_t<std::uint8_t, py::array::c_style | py::array::forcecast> const & wells,\n    py::array_t<std::int16_t, py::array::c_style | py::array::forcecast> const & pore_types,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & calibration_offsets,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & calibration_scales,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & median_befores,\n    py::array_t<std::int16_t, py::array::c_style | py::array::forcecast> const & end_reasons,\n    py::array_t<bool, py::array::c_style | py::array::forcecast> const & end_reason_forceds,\n    py::array_t<std::int16_t, py::array::c_style | py::array::forcecast> const & run_infos,\n    py::array_t<std::uint64_t, py::array::c_style | py::array::forcecast> const &\n        num_minknow_events,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & tracked_scaling_scale,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & tracked_scaling_shift,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & predicted_scaling_scale,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & predicted_scaling_shift,\n    py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> const &\n        num_reads_since_mux_change,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & time_since_mux_change,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & open_pore_level)\n{\n    auto read_ids = reinterpret_cast<pod5::Uuid const *>(read_id_data.data(0));\n    return pod5::ReadData{\n        read_ids[row_id],\n        *read_numbers.data(row_id),\n        *start_samples.data(row_id),\n        *channels.data(row_id),\n        *wells.data(row_id),\n        *pore_types.data(row_id),\n        *calibration_offsets.data(row_id),\n        *calibration_scales.data(row_id),\n        *median_befores.data(row_id),\n        *end_reasons.data(row_id),\n        *end_reason_forceds.data(row_id),\n        *run_infos.data(row_id),\n        *num_minknow_events.data(row_id),\n        *tracked_scaling_scale.data(row_id),\n        *tracked_scaling_shift.data(row_id),\n        *predicted_scaling_scale.data(row_id),\n        *predicted_scaling_shift.data(row_id),\n        *num_reads_since_mux_change.data(row_id),\n        *time_since_mux_change.data(row_id),\n        *open_pore_level.data(row_id)};\n}\n\ninline void FileWriter_add_reads(\n    pod5::FileWriter & w,\n    std::size_t count,\n    py::array_t<std::uint8_t, py::array::c_style | py::array::forcecast> const & read_id_data,\n    py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> const & read_numbers,\n    py::array_t<std::uint64_t, py::array::c_style | py::array::forcecast> const & start_samples,\n    py::array_t<std::uint16_t, py::array::c_style | py::array::forcecast> const & channels,\n    py::array_t<std::uint8_t, py::array::c_style | py::array::forcecast> const & wells,\n    py::array_t<std::int16_t, py::array::c_style | py::array::forcecast> const & pore_types,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & calibration_offsets,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & calibration_scales,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & median_befores,\n    py::array_t<std::int16_t, py::array::c_style | py::array::forcecast> const & end_reasons,\n    py::array_t<bool, py::array::c_style | py::array::forcecast> const & end_reason_forceds,\n    py::array_t<std::int16_t, py::array::c_style | py::array::forcecast> const & run_infos,\n    py::array_t<std::uint64_t, py::array::c_style | py::array::forcecast> const &\n        num_minknow_events,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & tracked_scaling_scales,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & tracked_scaling_shifts,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & predicted_scaling_scales,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & predicted_scaling_shifts,\n    py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> const &\n        num_reads_since_mux_changes,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & time_since_mux_changes,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & open_pore_levels,\n    py::list signal_ptrs)\n{\n    if (read_id_data.shape(1) != 16) {\n        throw std::runtime_error(\"Read id array is of unexpected size\");\n    }\n\n    auto signal_it = signal_ptrs.begin();\n    for (std::size_t i = 0; i < count; ++i, ++signal_it) {\n        if (signal_it == signal_ptrs.end()) {\n            throw std::runtime_error(\"Missing signal data\");\n        }\n        auto signal =\n            signal_it->cast<py::array_t<std::int16_t, py::array::c_style | py::array::forcecast>>();\n        auto signal_span = gsl::make_span(signal.data(), signal.size());\n\n        auto read_data = make_read_data(\n            i,\n            read_id_data,\n            read_numbers,\n            start_samples,\n            channels,\n            wells,\n            pore_types,\n            calibration_offsets,\n            calibration_scales,\n            median_befores,\n            end_reasons,\n            end_reason_forceds,\n            run_infos,\n            num_minknow_events,\n            tracked_scaling_scales,\n            tracked_scaling_shifts,\n            predicted_scaling_scales,\n            predicted_scaling_shifts,\n            num_reads_since_mux_changes,\n            time_since_mux_changes,\n            open_pore_levels);\n\n        throw_on_error(w.add_complete_read(read_data, signal_span));\n    }\n}\n\ninline void FileWriter_add_reads_pre_compressed(\n    pod5::FileWriter & w,\n    std::size_t count,\n    py::array_t<std::uint8_t, py::array::c_style | py::array::forcecast> const & read_id_data,\n    py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> const & read_numbers,\n    py::array_t<std::uint64_t, py::array::c_style | py::array::forcecast> const & start_samples,\n    py::array_t<std::uint16_t, py::array::c_style | py::array::forcecast> const & channels,\n    py::array_t<std::uint8_t, py::array::c_style | py::array::forcecast> const & wells,\n    py::array_t<std::int16_t, py::array::c_style | py::array::forcecast> const & pore_types,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & calibration_offsets,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & calibration_scales,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & median_befores,\n    py::array_t<std::int16_t, py::array::c_style | py::array::forcecast> const & end_reasons,\n    py::array_t<bool, py::array::c_style | py::array::forcecast> const & end_reason_forceds,\n    py::array_t<std::int16_t, py::array::c_style | py::array::forcecast> const & run_infos,\n    py::array_t<std::uint64_t, py::array::c_style | py::array::forcecast> const &\n        num_minknow_events,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & tracked_scaling_scales,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & tracked_scaling_shifts,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & predicted_scaling_scales,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & predicted_scaling_shifts,\n    py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> const &\n        num_reads_since_mux_changes,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & time_since_mux_changes,\n    py::array_t<float, py::array::c_style | py::array::forcecast> const & open_pore_levels,\n    py::list compressed_signal_ptrs,\n    py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> const & sample_counts,\n    py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> const &\n        signal_chunk_counts)\n{\n    if (read_id_data.shape(1) != 16) {\n        throw std::runtime_error(\"Read id array is of unexpected size\");\n    }\n\n    auto read_ids = reinterpret_cast<pod5::Uuid const *>(read_id_data.data(0));\n    auto compressed_signal_it = compressed_signal_ptrs.begin();\n    auto sample_counts_it = sample_counts.data();\n    for (std::size_t i = 0; i < count; ++i) {\n        auto const read_id = read_ids[i];\n\n        auto const signal_chunk_count = *signal_chunk_counts.data(i);\n        std::uint64_t signal_duration_count = 0;\n        std::vector<std::uint64_t> signal_rows(signal_chunk_count);\n        for (std::size_t signal_chunk_idx = 0; signal_chunk_idx < signal_chunk_count;\n             ++signal_chunk_idx)\n        {\n            if (compressed_signal_it == compressed_signal_ptrs.end()) {\n                throw std::runtime_error(\"Missing signal data\");\n            }\n            auto compressed_signal =\n                compressed_signal_it\n                    ->cast<py::array_t<std::uint8_t, py::array::c_style | py::array::forcecast>>();\n            auto compressed_signal_span =\n                gsl::make_span(compressed_signal.data(), compressed_signal.size());\n\n            auto signal_row = throw_on_error(\n                w.add_pre_compressed_signal(read_id, compressed_signal_span, *sample_counts_it));\n            signal_rows[signal_chunk_idx] = signal_row;\n\n            signal_duration_count += *sample_counts_it;\n            ++compressed_signal_it;\n            ++sample_counts_it;\n        }\n\n        auto read_data = make_read_data(\n            i,\n            read_id_data,\n            read_numbers,\n            start_samples,\n            channels,\n            wells,\n            pore_types,\n            calibration_offsets,\n            calibration_scales,\n            median_befores,\n            end_reasons,\n            end_reason_forceds,\n            run_infos,\n            num_minknow_events,\n            tracked_scaling_scales,\n            tracked_scaling_shifts,\n            predicted_scaling_scales,\n            predicted_scaling_shifts,\n            num_reads_since_mux_changes,\n            time_since_mux_changes,\n            open_pore_levels);\n\n        throw_on_error(w.add_complete_read(read_data, signal_rows, signal_duration_count));\n    }\n}\n\ninline void decompress_signal_wrapper(\n    py::array_t<uint8_t, py::array::c_style | py::array::forcecast> const & compressed_signal,\n    py::array_t<std::int16_t, py::array::c_style | py::array::forcecast> & signal_out)\n{\n    throw_on_error(\n        pod5::decompress_signal(\n            gsl::make_span(compressed_signal.data(0), compressed_signal.shape(0)),\n            arrow::system_memory_pool(),\n            gsl::make_span(signal_out.mutable_data(0), signal_out.shape(0))));\n}\n\ninline std::size_t compress_signal_wrapper(\n    py::array_t<std::int16_t, py::array::c_style | py::array::forcecast> const & signal,\n    py::array_t<std::uint8_t, py::array::c_style | py::array::forcecast> & compressed_signal_out)\n{\n    auto size = throw_on_error(\n        pod5::compress_signal(\n            gsl::make_span(signal.data(), signal.shape(0)),\n            arrow::system_memory_pool(),\n            gsl::make_span(compressed_signal_out.mutable_data(), compressed_signal_out.shape(0))));\n\n    return size;\n}\n\ninline std::size_t vbz_compressed_signal_max_size(std::size_t sample_count)\n{\n    POD5_PYTHON_ASSIGN_OR_RAISE(\n        std::size_t const max_size, pod5::compressed_signal_max_size(sample_count));\n    return max_size;\n}\n\ninline std::size_t load_read_id_iterable(\n    py::iterable const & read_ids_str,\n    py::array_t<std::uint8_t, py::array::c_style | py::array::forcecast> & read_id_data_out)\n{\n    std::size_t out_idx = 0;\n    auto read_ids = reinterpret_cast<pod5::Uuid *>(read_id_data_out.mutable_data());\n    auto read_ids_out_len = read_id_data_out.shape(0);\n\n    std::string temp_uuid;\n    for (auto & read_id : read_ids_str) {\n        if (out_idx >= (std::size_t)read_ids_out_len) {\n            throw std::runtime_error(\"Too many input uuids for output container\");\n        }\n\n        temp_uuid = read_id.cast<py::str>();\n        if (auto const found_uuid = pod5::Uuid::from_string(temp_uuid)) {\n            read_ids[out_idx++] = *found_uuid;\n        }\n        // if it's invalid, ignore it - we will return one fewer read ids than expected and the caller can deal with it.\n    }\n\n    return out_idx;\n}\n\ninline py::list format_read_id_to_str(\n    py::array_t<std::uint8_t, py::array::c_style | py::array::forcecast> & read_id_data_out)\n{\n    if (read_id_data_out.size() % 16 != 0) {\n        throw std::runtime_error(\n            \"Unexpected amount of data for read id - expected data to align to 16 bytes.\");\n    }\n\n    py::list result;\n\n    std::array<char, 37> str_data;\n    std::size_t const count = read_id_data_out.size() / 16;\n    for (std::size_t i = 0; i < count; ++i) {\n        auto read_id_data = read_id_data_out.data() + (i * 16);\n\n        pod5_format_read_id(read_id_data, str_data.data());\n        result.append(py::str(str_data.data(), str_data.size() - 1));\n    }\n\n    return result;\n}\n"
  },
  {
    "path": "c++/pod5_format_pybind/bindings.cpp",
    "content": "#include \"api.h\"\n#include \"pod5_format/c_api.h\"\n#include \"repack/repack_output.h\"\n#include \"repack/repacker.h\"\n#include \"subset.h\"\n\nPYBIND11_MODULE(pod5_format_pybind, m)\n{\n    using namespace pod5;\n    pod5_init();\n\n    m.doc() = \"POD5 Format Raw Bindings\";\n\n    py::enum_<SignalType>(m, \"SignalType\", py::arithmetic(), \"SignalType enum\")\n        .value(\"UncompressedSignal\", SignalType::UncompressedSignal, \"Signal is not compressed\")\n        .value(\"VbzSignal\", SignalType::VbzSignal, \"Signal is compressed using vbz\")\n        .export_values();\n\n    py::class_<FileWriterOptions>(m, \"FileWriterOptions\")\n        .def(py::init<>())\n        .def_property(\n            \"max_signal_chunk_size\",\n            &FileWriterOptions::max_signal_chunk_size,\n            &FileWriterOptions::set_max_signal_chunk_size)\n        .def_property(\n            \"signal_table_batch_size\",\n            &FileWriterOptions::signal_table_batch_size,\n            &FileWriterOptions::set_signal_table_batch_size)\n        .def_property(\n            \"read_table_batch_size\",\n            &FileWriterOptions::read_table_batch_size,\n            &FileWriterOptions::set_read_table_batch_size)\n        .def_property(\n            \"signal_compression_type\",\n            &FileWriterOptions::signal_type,\n            &FileWriterOptions::set_signal_type);\n\n    py::class_<FileWriter, std::shared_ptr<FileWriter>>(m, \"FileWriter\")\n        .def(\"close\", [](pod5::FileWriter & w) { throw_on_error(w.close()); })\n        .def(\n            \"add_pore\",\n            [](pod5::FileWriter & w, std::string pore_type) {\n                return throw_on_error(w.add_pore_type(std::move(pore_type)));\n            })\n        .def(\n            \"add_end_reason\",\n            [](pod5::FileWriter & w, int name) {\n                return throw_on_error(w.lookup_end_reason((pod5::ReadEndReason)name));\n            })\n        .def(\"add_run_info\", FileWriter_add_run_info)\n        .def(\"add_reads\", FileWriter_add_reads)\n        .def(\"add_reads_pre_compressed\", FileWriter_add_reads_pre_compressed);\n\n    py::class_<pod5::FileLocation>(m, \"EmbeddedFileData\")\n        .def_readonly(\"file_path\", &pod5::FileLocation::file_path)\n        .def_readonly(\"offset\", &pod5::FileLocation::offset)\n        .def_readonly(\"length\", &pod5::FileLocation::size);\n\n    py::class_<Pod5AsyncSignalLoader, std::shared_ptr<Pod5AsyncSignalLoader>>(\n        m, \"Pod5AsyncSignalLoader\")\n        .def(\"release_next_batch\", &Pod5AsyncSignalLoader::release_next_batch);\n\n    py::class_<Pod5SignalCacheBatch, std::shared_ptr<Pod5SignalCacheBatch>>(\n        m, \"Pod5SignalCacheBatch\")\n        .def_property_readonly(\"batch_index\", &Pod5SignalCacheBatch::batch_index)\n        .def_property_readonly(\"sample_count\", &Pod5SignalCacheBatch::sample_count)\n        .def_property_readonly(\"samples\", &Pod5SignalCacheBatch::samples);\n\n    py::class_<Pod5FileReaderPtr>(m, \"Pod5FileReader\")\n        .def(\n            \"get_file_run_info_table_location\",\n            &Pod5FileReaderPtr::get_file_run_info_table_location)\n        .def(\"get_file_read_table_location\", &Pod5FileReaderPtr::get_file_read_table_location)\n        .def(\"get_file_signal_table_location\", &Pod5FileReaderPtr::get_file_signal_table_location)\n        .def(\"get_file_version_pre_migration\", &Pod5FileReaderPtr::get_file_version_pre_migration)\n        .def(\"plan_traversal\", &Pod5FileReaderPtr::plan_traversal)\n        .def(\"batch_get_signal\", &Pod5FileReaderPtr::batch_get_signal)\n        .def(\"batch_get_signal_selection\", &Pod5FileReaderPtr::batch_get_signal_selection)\n        .def(\"batch_get_signal_batches\", &Pod5FileReaderPtr::batch_get_signal_batches)\n        .def(\"close\", &Pod5FileReaderPtr::close);\n\n    // Errors API\n    m.def(\"get_error_string\", &pod5_get_error_string, \"Get the most recent error as a string\");\n\n    // Creating files\n    m.def(\n        \"create_file\",\n        &create_file,\n        \"Create a POD5 file for writing\",\n        py::arg(\"filename\"),\n        py::arg(\"writer_name\"),\n        py::arg(\"options\") = nullptr);\n\n    // Opening files\n    m.def(\"open_file\", &open_file, \"Open a POD5 file for reading\");\n\n    // Recovering files\n    py::class_<RecoverFileOptions>(m, \"RecoverFileOptions\")\n        .def(py::init<>())\n        .def_readwrite(\"file_writer_options\", &RecoverFileOptions::file_writer_options)\n        .def_readwrite(\"cleanup\", &RecoverFileOptions::cleanup);\n    py::class_<RecoveredRowCounts>(m, \"RecoveredRowCounts\")\n        .def(py::init<>())\n        .def_readwrite(\"signal\", &RecoveredRowCounts::signal)\n        .def_readwrite(\"run_info\", &RecoveredRowCounts::run_info)\n        .def_readwrite(\"reads\", &RecoveredRowCounts::reads);\n    py::class_<CleanupError>(m, \"CleanupError\")\n        .def(py::init<>())\n        .def_readwrite(\"file_path\", &CleanupError::file_path)\n        .def_readwrite(\"description\", &CleanupError::description);\n    py::class_<RecoveryDetails>(m, \"RecoveryDetails\")\n        .def(py::init<>())\n        .def_readwrite(\"row_counts\", &RecoveryDetails::row_counts)\n        .def_readwrite(\"cleanup_errors\", &RecoveryDetails::cleanup_errors);\n    m.def(\n        \"recover_file\",\n        &::recover_file,\n        \"Recover a POD5 file which was not closed correctly\",\n        py::arg(\"src_filename\"),\n        py::arg(\"dest_filename\"),\n        py::arg(\"options\") = nullptr);\n\n    m.def(\n        \"update_file\",\n        &write_updated_file_to_dest,\n        \"Update a POD5 file to the latest writer format\");\n\n    // Signal API\n    m.def(\"decompress_signal\", &decompress_signal_wrapper, \"Decompress a numpy array of signal\");\n    m.def(\"compress_signal\", &compress_signal_wrapper, \"Compress a numpy array of signal\");\n    m.def(\"vbz_compressed_signal_max_size\", &vbz_compressed_signal_max_size);\n\n    // Repacker API\n    py::class_<repack::Pod5RepackerOutput, std::shared_ptr<repack::Pod5RepackerOutput>>(\n        m, \"Pod5RepackerOutput\");\n\n    py::class_<repack::Pod5Repacker, std::shared_ptr<repack::Pod5Repacker>>(m, \"Repacker\")\n        .def(py::init<>())\n        .def(\"add_output\", &repack::Pod5Repacker::add_output)\n        .def(\"set_output_finished\", &repack::Pod5Repacker::set_output_finished)\n        .def(\"add_all_reads_to_output\", &repack::Pod5Repacker::add_all_reads_to_output)\n        .def(\"add_selected_reads_to_output\", &repack::Pod5Repacker::py_add_selected_reads_to_output)\n        .def(\"finish\", &repack::Pod5Repacker::finish)\n        .def_property_readonly(\"is_complete\", &repack::Pod5Repacker::is_complete)\n        .def_property_readonly(\n            \"currently_open_file_reader_count\",\n            &repack::Pod5Repacker::currently_open_file_reader_count)\n        .def_property_readonly(\"reads_completed\", &repack::Pod5Repacker::reads_completed);\n\n    // Util API\n    m.def(\n        \"load_read_id_iterable\",\n        &load_read_id_iterable,\n        \"Load an iterable of read ids into a numpy array of data\");\n    m.def(\"format_read_id_to_str\", &format_read_id_to_str, \"Format an array of read ids to string\");\n\n    m.def(\n        \"subset_pod5s_with_mapping\",\n        &subset_pod5s_with_mapping,\n        \"Subset pod5 files given a mapping\");\n}\n"
  },
  {
    "path": "c++/pod5_format_pybind/build_wheel.cmake",
    "content": "\nmessage(\"Building python lib-pod5 wheel using ${PYTHON_EXECUTABLE}\")\nmessage(\"  project dir ${PYTHON_PROJECT_DIR}\")\nmessage(\"  with lib ${PYBIND_INPUT_LIB}\")\nmessage(\"  with conan licenses ${POD5_CONAN_LICENSES}\")\nmessage(\"  with c++ licenses ${POD5_CXX_LICENSES_SRC}\")\nmessage(\"  into ${WHEEL_OUTPUT_DIR}\")\nmessage(\"  using: ${PYTHON_EXECUTABLE} -m pip wheel . --wheel-dir ${WHEEL_OUTPUT_DIR}\")\n\n# Copy the prebuilt lib into the wheel src.\nfile(COPY \"${PYBIND_INPUT_LIB}\" DESTINATION \"${PYTHON_PROJECT_DIR}/src/lib_pod5\")\n\n# Copy the licenses into the wheel src.\n# Note: the trailing / on src is important since it tells cmake to copy only the contents.\nif(EXISTS \"${POD5_CONAN_LICENSES}\")\n    file(INSTALL \"${POD5_CONAN_LICENSES}/\" DESTINATION \"${PYTHON_PROJECT_DIR}/licenses\")\nendif()\n\nforeach(license_src license_dst IN ZIP_LISTS POD5_CXX_LICENSES_SRC POD5_CXX_LICENSES_DST)\n    file(COPY_FILE \"${license_src}\" \"${PYTHON_PROJECT_DIR}/licenses/${license_dst}\")\nendforeach()\n\nexecute_process(\n    COMMAND ${PYTHON_EXECUTABLE} -m pip wheel . --wheel-dir ${WHEEL_OUTPUT_DIR}\n    WORKING_DIRECTORY \"${PYTHON_PROJECT_DIR}/\"\n    RESULT_VARIABLE exit_code\n    OUTPUT_VARIABLE output\n    ERROR_VARIABLE output\n)\n\nif (NOT exit_code EQUAL 0)\n    message(FATAL_ERROR \"Could not generate wheel: ${output}\")\nendif()\n\nfile(GLOB pod5_wheel_names \"${WHEEL_OUTPUT_DIR}/*.whl\")\nforeach(wheel ${pod5_wheel_names})\n    message(\"Built wheel ${wheel}\")\nendforeach()\n"
  },
  {
    "path": "c++/pod5_format_pybind/repack/repack_functions.h",
    "content": "#pragma once\n\n#include \"pod5_format/internal/tracing/tracing.h\"\n#include \"pod5_format/read_table_reader.h\"\n#include \"pod5_format/signal_builder.h\"\n#include \"pod5_format/signal_table_schema.h\"\n#include \"pod5_format/uuid.h\"\n#include \"repack_utils.h\"\n\n#include <arrow/array/array_nested.h>\n#include <arrow/array/array_primitive.h>\n#include <arrow/array/builder_binary.h>\n\n#include <numeric>\n#include <unordered_set>\n\nnamespace repack {\n\nstruct ReadReadData {\n    std::shared_ptr<pod5::FileReader> input;\n    std::vector<pod5::ReadData> reads;\n    std::vector<std::size_t> signal_durations;\n    std::vector<std::size_t> signal_row_sizes;\n\n    std::vector<pod5::Uuid> signal_rows_read_ids;\n    std::vector<std::uint64_t> signal_rows;\n};\n\narrow::Result<ReadReadData> read_read_data(\n    ReadsTableDictionaryThreadCache & reads_table_cache,\n    states::unread_read_table_rows && in_batch)\n{\n    POD5_TRACE_FUNCTION();\n\n    auto const & source_file = in_batch.input;\n    ARROW_ASSIGN_OR_RAISE(\n        auto source_read_table_batch, source_file->read_read_record_batch(in_batch.batch_index));\n\n    ARROW_ASSIGN_OR_RAISE(auto columns, source_read_table_batch.columns());\n\n    auto const & pore_type_columns = columns.pore_type->indices();\n    auto const & source_reads_pore_type_column =\n        static_cast<arrow::Int16Array const &>(*pore_type_columns);\n    auto const & end_reason_columns = columns.end_reason->indices();\n    auto const & source_reads_end_reason_column =\n        static_cast<arrow::Int16Array const &>(*end_reason_columns);\n    auto const & run_info_columns = columns.run_info->indices();\n    auto const & source_reads_run_info_column =\n        static_cast<arrow::Int16Array const &>(*run_info_columns);\n    auto source_reads_signal_column = source_read_table_batch.signal_column();\n\n    auto batch_rows = std::move(in_batch.batch_rows);\n    if (batch_rows.empty()) {\n        auto const source_batch_row_count = source_read_table_batch.num_rows();\n        batch_rows.resize(source_batch_row_count);\n        std::iota(batch_rows.begin(), batch_rows.end(), 0);\n    }\n\n    ReadReadData result;\n    result.input = source_file;\n    result.reads.reserve(batch_rows.size());\n    result.signal_rows.reserve(batch_rows.size());\n    result.signal_row_sizes.reserve(batch_rows.size());\n    for (std::size_t batch_row_index = 0; batch_row_index < batch_rows.size(); ++batch_row_index) {\n        auto batch_row = batch_rows[batch_row_index];\n        // Find the read params\n        auto const & read_id = columns.read_id->Value(batch_row);\n        auto const & read_number = columns.read_number->Value(batch_row);\n        auto const & start_sample = columns.start_sample->Value(batch_row);\n        auto const & channel = columns.channel->Value(batch_row);\n        auto const & well = columns.well->Value(batch_row);\n        auto const & calibration_offset = columns.calibration_offset->Value(batch_row);\n        auto const & calibration_scale = columns.calibration_scale->Value(batch_row);\n        auto const & median_before = columns.median_before->Value(batch_row);\n        auto const & end_reason_forced = columns.end_reason_forced->Value(batch_row);\n        auto const & num_minknow_events = columns.num_minknow_events->Value(batch_row);\n        auto const & tracked_scaling_scale = columns.tracked_scaling_scale->Value(batch_row);\n        auto const & tracked_scaling_shift = columns.tracked_scaling_shift->Value(batch_row);\n        auto const & predicted_scaling_scale = columns.predicted_scaling_scale->Value(batch_row);\n        auto const & predicted_scaling_shift = columns.predicted_scaling_shift->Value(batch_row);\n        auto const & num_reads_since_mux_change =\n            columns.num_reads_since_mux_change->Value(batch_row);\n        auto const & time_since_mux_change = columns.time_since_mux_change->Value(batch_row);\n        auto const & open_pore_level = columns.open_pore_level->Value(batch_row);\n        auto const & num_samples = columns.num_samples->Value(batch_row);\n\n        auto const & pore_type_index = source_reads_pore_type_column.Value(batch_row);\n        auto const & end_reason_index = source_reads_end_reason_column.Value(batch_row);\n        auto const & run_info_index = source_reads_run_info_column.Value(batch_row);\n\n        ARROW_ASSIGN_OR_RAISE(\n            auto dest_pore_index,\n            reads_table_cache.find_pore_index(\n                source_file, source_read_table_batch, pore_type_index));\n        ARROW_ASSIGN_OR_RAISE(\n            auto dest_run_info_index,\n            reads_table_cache.find_run_info_index(\n                source_file, source_read_table_batch, run_info_index));\n\n        result.reads.emplace_back(\n            read_id,\n            read_number,\n            start_sample,\n            channel,\n            well,\n            dest_pore_index,\n            calibration_offset,\n            calibration_scale,\n            median_before,\n            end_reason_index,\n            end_reason_forced,\n            dest_run_info_index,\n            num_minknow_events,\n            tracked_scaling_scale,\n            tracked_scaling_shift,\n            predicted_scaling_scale,\n            predicted_scaling_shift,\n            num_reads_since_mux_change,\n            time_since_mux_change,\n            open_pore_level);\n        result.signal_durations.emplace_back(num_samples);\n\n        auto const signal_rows = std::static_pointer_cast<arrow::UInt64Array>(\n            source_reads_signal_column->value_slice(batch_row));\n        auto const signal_rows_span =\n            gsl::make_span(signal_rows->raw_values(), signal_rows->length());\n\n        result.signal_rows.insert(\n            result.signal_rows.end(), signal_rows_span.begin(), signal_rows_span.end());\n        for (std::size_t i = 0; i < signal_rows_span.size(); ++i) {\n            result.signal_rows_read_ids.emplace_back(read_id);\n        }\n        result.signal_row_sizes.emplace_back(signal_rows_span.size());\n    }\n    return result;\n}\n\narrow::Status read_signal(\n    std::shared_ptr<pod5::FileReader> const & source_file,\n    pod5::SignalType input_compression_type,\n    std::uint64_t abs_signal_row,\n    pod5::Uuid read_id,\n    pod5::SignalType output_compression_type,\n    arrow::FixedSizeBinaryBuilder & read_id_builder,\n    pod5::SignalBuilderVariant & signal_builder,\n    arrow::UInt32Builder & samples_builder,\n    arrow::MemoryPool * pool)\n{\n    auto signal_rows_span = gsl::make_span(&abs_signal_row, 1);\n\n    // If were using the same compression type in both files, just copy compressed:\n    if (input_compression_type == output_compression_type\n        && output_compression_type == pod5::SignalType::VbzSignal)\n    {\n        std::vector<uint32_t> sample_counts;\n        ARROW_ASSIGN_OR_RAISE(\n            auto extracted_signal,\n            source_file->extract_samples_inplace(signal_rows_span, sample_counts));\n\n        assert(1 == extracted_signal.size());\n        assert(sample_counts.size() == extracted_signal.size());\n        auto signal_span =\n            gsl::make_span(extracted_signal.front()->data(), extracted_signal.front()->size());\n\n        ARROW_RETURN_NOT_OK(read_id_builder.Append(read_id.data()));\n        ARROW_RETURN_NOT_OK(\n            std::visit(pod5::visitors::append_pre_compressed_signal{signal_span}, signal_builder));\n        ARROW_RETURN_NOT_OK(samples_builder.Append(sample_counts.front()));\n    } else {\n        // Find the sample count of the complete read:\n        ARROW_ASSIGN_OR_RAISE(\n            auto sample_count, source_file->extract_sample_count(signal_rows_span));\n\n        std::vector<std::int16_t> signal(sample_count);\n        auto signal_buffer_span = gsl::make_span(signal);\n        ARROW_RETURN_NOT_OK(source_file->extract_samples(signal_rows_span, signal_buffer_span));\n\n        ARROW_RETURN_NOT_OK(read_id_builder.Append(read_id.data()));\n        ARROW_RETURN_NOT_OK(\n            std::visit(pod5::visitors::append_signal{signal_buffer_span, pool}, signal_builder));\n        ARROW_RETURN_NOT_OK(samples_builder.Append(sample_count));\n    }\n    return arrow::Status::OK();\n}\n\nstruct RequestedSignalReads {\n    std::vector<states::shared_variant> complete_requests;\n    std::shared_ptr<states::read_split_signal_table_batch_rows> partial_request;\n};\n\narrow::Result<RequestedSignalReads> request_signal_reads(\n    std::shared_ptr<pod5::FileReader> const & source_file,\n    pod5::SignalType output_compression_type,\n    std::size_t signal_batch_size,\n    std::vector<pod5::Uuid> read_ids,\n    std::vector<std::uint64_t> signal_rows,\n    std::shared_ptr<states::read_split_signal_table_batch_rows> const & partial_request,\n    std::shared_ptr<states::read_read_table_rows_no_signal> const & dest_read_table_rows,\n    arrow::MemoryPool * pool)\n{\n    POD5_TRACE_FUNCTION();\n\n    auto const input_signal_type = source_file->signal_type();\n\n    assert(read_ids.size() == signal_rows.size());\n\n    RequestedSignalReads result;\n    auto next_request = partial_request;\n\n    assert(signal_rows.size() == dest_read_table_rows->signal_row_indices.size());\n\n    std::size_t signal_rows_position = 0;\n    while (signal_rows_position < signal_rows.size()) {\n        if (!next_request) {\n            ARROW_ASSIGN_OR_RAISE(\n                auto signal_builder, pod5::make_signal_builder(output_compression_type, pool));\n            next_request = std::make_shared<states::read_split_signal_table_batch_rows>(\n                std::move(signal_builder), pool);\n            next_request->patch_rows.reserve(signal_batch_size);\n        }\n        auto to_write = std::min(\n            signal_rows.size() - signal_rows_position,\n            signal_batch_size - next_request->patch_rows.size());\n\n        for (std::size_t i = 0; i < to_write; ++i) {\n            auto const dest_batch_row_index = signal_rows_position + i;\n            assert(dest_batch_row_index < signal_rows.size());\n            assert(dest_batch_row_index < dest_read_table_rows->signal_row_indices.size());\n\n            ARROW_RETURN_NOT_OK(read_signal(\n                source_file,\n                input_signal_type,\n                signal_rows[signal_rows_position + i],\n                read_ids[signal_rows_position + i],\n                output_compression_type,\n                *next_request->read_id_builder,\n                next_request->signal_builder,\n                next_request->samples_builder,\n                pool));\n\n            next_request->patch_rows.emplace_back(dest_read_table_rows, dest_batch_row_index);\n        }\n        signal_rows_position += to_write;\n\n        assert(next_request->row_count() <= signal_batch_size);\n        assert(next_request->row_count() <= signal_batch_size);\n        if (next_request->row_count() >= signal_batch_size) {\n            result.complete_requests.emplace_back(std::move(next_request));\n            next_request.reset();\n        }\n    }\n\n    result.partial_request = next_request;\n    return result;\n}\n\nstruct ReadSignal {\n    std::size_t row_count;\n    bool final_batch;\n    std::vector<std::shared_ptr<arrow::Array>> columns;\n};\n\narrow::Result<ReadSignal> read_signal_data(states::read_split_signal_table_batch_rows & signal_rows)\n{\n    POD5_TRACE_FUNCTION();\n\n    ReadSignal result;\n\n    pod5::SignalTableSchemaDescription field_locations;\n    result.final_batch = signal_rows.final_batch;\n    result.row_count = signal_rows.row_count();\n    result.columns = {nullptr, nullptr, nullptr};\n    ARROW_RETURN_NOT_OK(\n        signal_rows.read_id_builder->Finish(&result.columns[field_locations.read_id]));\n    ARROW_RETURN_NOT_OK(\n        std::visit(\n            pod5::visitors::finish_column{&result.columns[field_locations.signal]},\n            signal_rows.signal_builder));\n    ARROW_RETURN_NOT_OK(\n        signal_rows.samples_builder.Finish(&result.columns[field_locations.samples]));\n    return result;\n}\n\narrow::Status write_reads(\n    std::shared_ptr<pod5::FileWriter> const & output,\n    std::vector<pod5::ReadData> const & reads,\n    std::vector<std::size_t> const & signal_durations,\n    std::vector<std::size_t> const & signal_row_sizes,\n    std::vector<pod5::SignalTableRowIndex> const & signal_row_indices)\n{\n    POD5_TRACE_FUNCTION();\n    std::size_t signal_position = 0;\n    auto signal_indices_span = gsl::make_span(signal_row_indices);\n    for (std::size_t i = 0; i < reads.size(); ++i) {\n        auto signal_rows = signal_indices_span.subspan(signal_position, signal_row_sizes[i]);\n        signal_position += signal_row_sizes[i];\n\n        ARROW_RETURN_NOT_OK(output->add_complete_read(reads[i], signal_rows, signal_durations[i]));\n    }\n\n    return arrow::Status::OK();\n}\n\narrow::Status check_duplicate_read_ids(\n    std::unordered_set<pod5::Uuid> & output_read_ids,\n    std::vector<pod5::ReadData> const & new_reads)\n{\n    for (auto const & read : new_reads) {\n        auto result = output_read_ids.insert(read.read_id);\n        if (!result.second) {\n            return arrow::Status::Invalid(\n                \"Duplicate read id \", to_string(read.read_id), \" found in file\");\n        }\n    }\n\n    return arrow::Status::OK();\n}\n\n}  // namespace repack\n"
  },
  {
    "path": "c++/pod5_format_pybind/repack/repack_output.cpp",
    "content": "#include \"repack_output.h\"\n\n#include \"pod5_format/internal/tracing/tracing.h\"\n#include \"repack_functions.h\"\n\n#include <iostream>\n#include <thread>\n#include <unordered_set>\n\nnamespace repack {\n\nnamespace {\nstruct is_not_nullptr {\n    template <typename T>\n    bool operator()(T const & t) const\n    {\n        return t != nullptr;\n    }\n};\n\n#if 0\nstruct get_name{\n    template <typename T>\n    std::string operator()(T const& t) const {\n        return typeid(typename T::element_type).name();\n    }\n};\n\ntemplate <typename T>\nvoid dump_queued_items(T const& queued) {\n    std::map<std::string, std::size_t> items;\n\n    for (auto const& item : queued) {\n        items[std::visit(get_name{}, item)] += 1;\n    }\n\n    std::cout << \"Queued items:\\n\";\n    for (auto pr : items) {\n        std::cout << \"  \" << pr.first << \": \" << pr.second << \"\\n\";\n    }\n}\n#endif\n\n}  // namespace\n\nstruct Pod5RepackerOutputThreadState {\n    Pod5RepackerOutputThreadState(std::shared_ptr<ReadsTableDictionaryManager> const & dict_manager)\n    : dict_cache(dict_manager)\n    {\n    }\n\n    ReadsTableDictionaryThreadCache dict_cache;\n};\n\nstruct Pod5RepackerOutputState {\n    Pod5RepackerOutputState(\n        std::shared_ptr<pod5::FileWriter> const & _output_file,\n        bool _check_duplicate_read_ids,\n        arrow::MemoryPool * _memory_pool)\n    : output_file(_output_file)\n    , check_duplicate_read_ids(_check_duplicate_read_ids)\n    , memory_pool(_memory_pool)\n    , dict_manager(\n          std::make_shared<ReadsTableDictionaryManager>(_output_file, read_table_writer_mutex))\n    {\n    }\n\n    Pod5RepackerOutputThreadState * get_thread_state()\n    {\n        std::lock_guard<std::mutex> l{thread_states_mutex};\n        auto it = thread_states.find(std::this_thread::get_id());\n        if (it == thread_states.end()) {\n            it = thread_states.emplace(std::this_thread::get_id(), dict_manager).first;\n        }\n        return &it->second;\n    }\n\n    std::shared_ptr<pod5::FileWriter> output_file;\n    bool check_duplicate_read_ids;\n    arrow::MemoryPool * memory_pool;\n    std::mutex read_table_writer_mutex;\n    std::mutex signal_table_writer_mutex;\n    std::shared_ptr<ReadsTableDictionaryManager> dict_manager;\n    std::mutex partial_signal_batch_mutex;\n    std::shared_ptr<states::read_split_signal_table_batch_rows> partial_signal_batch;\n    std::atomic<std::size_t> reads_completed{0};\n\n    std::mutex thread_states_mutex;\n    std::unordered_map<std::thread::id, Pod5RepackerOutputThreadState> thread_states;\n\n    std::mutex output_read_ids_mutex;\n    std::unordered_set<pod5::Uuid> output_read_ids;\n};\n\nnamespace {\n\nstruct StateProgressResult {\n    StateProgressResult() = default;\n\n    StateProgressResult(std::vector<states::shared_variant> && _new_states)\n    : new_states(_new_states)\n    {\n    }\n\n    std::vector<states::shared_variant> new_states;\n};\n\nstruct StateOperator {\n    StateOperator(Pod5RepackerOutputState * _progress_state) : progress_state(_progress_state) {}\n\n    arrow::Result<StateProgressResult> operator()(\n        std::shared_ptr<states::unread_read_table_rows> & batch) const\n    {\n        POD5_TRACE_FUNCTION();\n\n        // Read out the read table data from the source file\n        ARROW_ASSIGN_OR_RAISE(\n            auto read_result,\n            read_read_data(progress_state->get_thread_state()->dict_cache, std::move(*batch)));\n        batch.reset();\n\n        auto read_table_rows = std::make_shared<states::read_read_table_rows_no_signal>();\n        read_table_rows->reads = std::move(read_result.reads);\n        read_table_rows->signal_durations = std::move(read_result.signal_durations);\n        read_table_rows->signal_row_sizes = std::move(read_result.signal_row_sizes);\n        read_table_rows->signal_row_indices.resize(read_result.signal_rows.size());\n\n        if (progress_state->check_duplicate_read_ids) {\n            std::lock_guard<std::mutex> l{progress_state->output_read_ids_mutex};\n            ARROW_RETURN_NOT_OK(\n                check_duplicate_read_ids(progress_state->output_read_ids, read_table_rows->reads));\n        }\n\n        // Split the read table rows into new signal table batches:\n        {\n            std::lock_guard<std::mutex> l{progress_state->partial_signal_batch_mutex};\n            ARROW_ASSIGN_OR_RAISE(\n                auto signal_request_result,\n                request_signal_reads(\n                    read_result.input,\n                    progress_state->output_file->signal_type(),\n                    progress_state->output_file->signal_table_batch_size(),\n                    read_result.signal_rows_read_ids,\n                    read_result.signal_rows,\n                    progress_state->partial_signal_batch,\n                    read_table_rows,\n                    progress_state->memory_pool));\n\n            progress_state->partial_signal_batch = signal_request_result.partial_request;\n            return StateProgressResult{std::move(signal_request_result.complete_requests)};\n        }\n    }\n\n    arrow::Result<StateProgressResult> operator()(\n        std::shared_ptr<states::read_split_signal_table_batch_rows> & batch) const\n    {\n        POD5_TRACE_FUNCTION();\n\n        ARROW_ASSIGN_OR_RAISE(auto read_signal_result, read_signal_data(*batch));\n\n        std::pair<pod5::SignalTableRowIndex, pod5::SignalTableRowIndex> inserted_signal_rows;\n        {\n            std::lock_guard<std::mutex> l(progress_state->signal_table_writer_mutex);\n            ARROW_ASSIGN_OR_RAISE(\n                inserted_signal_rows,\n                progress_state->output_file->add_signal_batch(\n                    read_signal_result.row_count,\n                    std::move(read_signal_result.columns),\n                    read_signal_result.final_batch));\n        }\n\n        std::vector<states::shared_variant> result_new_states;\n\n        for (std::size_t i = 0; i < batch->patch_rows.size(); ++i) {\n            auto const & row = batch->patch_rows[i];\n\n            auto const & dest_read_table = row.dest_read_table;\n            assert(dest_read_table);\n            assert(row.dest_batch_row_index < dest_read_table->signal_row_indices.size());\n            dest_read_table->signal_row_indices[row.dest_batch_row_index] =\n                inserted_signal_rows.first + i;\n            dest_read_table->written_row_indices += 1;\n\n            // Check if this read table is completed!\n            if (dest_read_table->written_row_indices > dest_read_table->signal_row_indices.size()) {\n                assert(false);\n            }\n            if (dest_read_table->written_row_indices == dest_read_table->signal_row_indices.size())\n            {\n                result_new_states.push_back(dest_read_table);\n            }\n        }\n\n        return StateProgressResult{std::move(result_new_states)};\n    }\n\n    arrow::Result<StateProgressResult> operator()(\n        std::shared_ptr<states::read_read_table_rows_no_signal> & batch) const\n    {\n        POD5_TRACE_FUNCTION();\n        assert(batch->written_row_indices == batch->signal_row_indices.size());\n\n        std::lock_guard<std::mutex> l(progress_state->read_table_writer_mutex);\n        ARROW_RETURN_NOT_OK(write_reads(\n            progress_state->output_file,\n            batch->reads,\n            batch->signal_durations,\n            batch->signal_row_sizes,\n            batch->signal_row_indices));\n        progress_state->reads_completed += batch->reads.size();\n\n        return StateProgressResult{{}};\n    }\n\n    arrow::Result<StateProgressResult> operator()(std::shared_ptr<states::finished> & batch) const\n    {\n        POD5_TRACE_FUNCTION();\n\n        std::vector<states::shared_variant> final_states;\n        // No further reads expected, flush all partial state:\n        std::lock_guard<std::mutex> l{progress_state->partial_signal_batch_mutex};\n        if (progress_state->partial_signal_batch) {\n            progress_state->partial_signal_batch->final_batch = true;\n\n            final_states.emplace_back(std::move(progress_state->partial_signal_batch));\n            progress_state->partial_signal_batch.reset();\n        }\n\n        return StateProgressResult{std::move(final_states)};\n    }\n\n    Pod5RepackerOutputState * progress_state;\n};\n\n}  // namespace\n\nPod5RepackerOutput::Pod5RepackerOutput(\n    std::shared_ptr<Pod5Repacker> const & repacker,\n    std::shared_ptr<pod5::ThreadPool> thread_pool,\n    std::shared_ptr<pod5::FileWriter> const & output,\n    bool check_duplicate_read_ids)\n: m_repacker(repacker)\n, m_thread_pool(thread_pool)\n, m_output(output)\n, m_progress_state(\n      std::make_unique<Pod5RepackerOutputState>(\n          output,\n          check_duplicate_read_ids,\n          arrow::default_memory_pool()))\n{\n}\n\nPod5RepackerOutput::~Pod5RepackerOutput() {}\n\nbool Pod5RepackerOutput::has_tasks() const\n{\n    if (m_in_flight > 0) {\n        return true;\n    }\n    std::lock_guard<std::mutex> l{m_active_read_table_states_mutex};\n    return m_active_read_table_states.size() > 0;\n}\n\nvoid Pod5RepackerOutput::set_finished()\n{\n    if (!m_finished) {\n        // Wait for all other tasks to flush through the output.\n        while (!m_has_error) {\n            if (!has_tasks()) {\n                break;\n            }\n\n            std::this_thread::sleep_for(std::chrono::milliseconds(1));\n        }\n\n        {\n            std::lock_guard<std::mutex> l{m_active_read_table_states_mutex};\n            m_active_read_table_states.emplace_front(std::make_shared<states::finished>());\n        }\n        post_try_work();\n\n        m_finished = true;\n    }\n}\n\nbool Pod5RepackerOutput::is_complete() const\n{\n    if (!m_finished) {\n        return false;\n    }\n    std::lock_guard<std::mutex> l{m_active_read_table_states_mutex};\n    return m_active_read_table_states.empty();\n}\n\nstd::size_t Pod5RepackerOutput::reads_completed() const\n{\n    return m_progress_state->reads_completed;\n}\n\nvoid Pod5RepackerOutput::register_new_reads(\n    std::shared_ptr<pod5::FileReader> const & input,\n    std::size_t batch_index,\n    std::vector<std::uint32_t> && batch_rows)\n{\n    if (m_finished) {\n        throw std::runtime_error(\"Failed to add reads to finished output\");\n    }\n\n    {\n        std::lock_guard<std::mutex> l{m_active_read_table_states_mutex};\n        m_active_read_table_states.emplace_front(\n            std::make_shared<states::unread_read_table_rows>(\n                input, batch_index, std::move(batch_rows)));\n    }\n\n    post_try_work();\n}\n\nvoid Pod5RepackerOutput::post_try_work()\n{\n    m_thread_pool->post([&]() {\n        POD5_TRACE_FUNCTION();\n\n        auto get_next_work = [](auto & locked_states) -> states::shared_variant {\n            if (locked_states.empty()) {\n                return {};\n            }\n\n            auto work = locked_states.back();\n            locked_states.pop_back();\n            return work;\n        };\n\n        StateOperator state_operator{m_progress_state.get()};\n\n        states::shared_variant next_work;\n        while (!m_has_error) {\n            m_in_flight += 1;\n            // Its important we don't release this until any new states\n            // are in `m_active_read_table_states`\n            auto remove_in_flight = gsl::finally([&] { m_in_flight -= 1; });\n\n            if (!std::visit(is_not_nullptr{}, next_work)) {\n                std::lock_guard<std::mutex> l{m_active_read_table_states_mutex};\n                next_work = get_next_work(m_active_read_table_states);\n                if (!std::visit(is_not_nullptr{}, next_work)) {\n                    return;\n                }\n            }\n\n            auto result = std::visit(state_operator, next_work);\n            if (!result.ok()) {\n                set_error(result.status());\n                return;\n            }\n            next_work = {};\n\n            {\n                std::lock_guard<std::mutex> l{m_active_read_table_states_mutex};\n                auto && states = m_active_read_table_states;\n                states.insert(states.end(), result->new_states.begin(), result->new_states.end());\n\n                next_work = get_next_work(states);\n            }\n        }\n    });\n}\n\n}  // namespace repack\n"
  },
  {
    "path": "c++/pod5_format_pybind/repack/repack_output.h",
    "content": "#pragma once\n\n#include \"pod5_format/file_writer.h\"\n#include \"pod5_format/thread_pool.h\"\n#include \"repack_states.h\"\n\n#include <atomic>\n#include <deque>\n#include <memory>\n#include <mutex>\n#include <vector>\n\nnamespace repack {\n\nclass Pod5Repacker;\n\nstruct Pod5RepackerOutputState;\n\nclass Pod5RepackerOutput {\npublic:\n    Pod5RepackerOutput(\n        std::shared_ptr<Pod5Repacker> const & repacker,\n        std::shared_ptr<pod5::ThreadPool> thread_pool,\n        std::shared_ptr<pod5::FileWriter> const & output,\n        bool check_duplicate_read_ids);\n    ~Pod5RepackerOutput();\n\n    std::string path() const { return m_output->path(); }\n\n    std::shared_ptr<Pod5Repacker> const & repacker() const { return m_repacker; }\n\n    bool has_tasks() const;\n\n    arrow::Status error()\n    {\n        std::lock_guard<std::mutex> l{m_error_mutex};\n        return m_error;\n    }\n\n    bool has_error() const { return m_has_error.load(); }\n\n    // Inform the output no further reads will be added\n    void set_finished();\n\n    // Check if the output has completed all writes\n    bool is_complete() const;\n\n    // Number of reads completed\n    std::size_t reads_completed() const;\n\n    // Register new writes to the output, should not be called after #set_reads_finished\n    void register_new_reads(\n        std::shared_ptr<pod5::FileReader> const & input,\n        std::size_t batch_index,\n        std::vector<std::uint32_t> && batch_rows = {}  // All rows by default\n    );\n\nprivate:\n    void post_try_work();\n\n    void set_error(arrow::Status error)\n    {\n        assert(!error.ok());\n        {\n            std::lock_guard<std::mutex> l{m_error_mutex};\n            m_error = std::move(error);\n        }\n        m_has_error = true;\n    }\n\n    std::shared_ptr<Pod5Repacker> m_repacker;\n    std::shared_ptr<pod5::ThreadPool> m_thread_pool;\n    std::shared_ptr<pod5::FileWriter> m_output;\n    std::atomic<bool> m_finished{false};\n\n    std::atomic<bool> m_has_error{false};\n    std::mutex m_error_mutex;\n    arrow::Status m_error;\n\n    std::atomic<std::size_t> m_in_flight{0};\n    mutable std::mutex m_active_read_table_states_mutex;\n    std::deque<states::shared_variant> m_active_read_table_states;\n\n    std::unique_ptr<Pod5RepackerOutputState> m_progress_state;\n};\n\n}  // namespace repack\n"
  },
  {
    "path": "c++/pod5_format_pybind/repack/repack_states.h",
    "content": "#pragma once\n\n#include \"pod5_format/file_reader.h\"\n#include \"pod5_format/signal_builder.h\"\n\n#include <arrow/array/builder_binary.h>\n#include <arrow/array/builder_primitive.h>\n\n#include <cstddef>\n#include <cstdint>\n#include <variant>\n#include <vector>\n\nnamespace repack { namespace states {\n\nclass unread_read_table_rows {\npublic:\n    unread_read_table_rows(\n        std::shared_ptr<pod5::FileReader> const & _input,\n        std::size_t _batch_index,\n        std::vector<std::uint32_t> && _batch_rows)\n    : input(_input)\n    , batch_index(_batch_index)\n    , batch_rows(std::move(_batch_rows))\n    {\n    }\n\n    std::shared_ptr<pod5::FileReader> input;\n    std::size_t batch_index;\n    std::vector<std::uint32_t> batch_rows;\n};\n\nclass read_read_table_rows_no_signal {\npublic:\n    std::vector<pod5::ReadData> reads;\n    std::vector<std::size_t> signal_durations;\n    std::vector<std::size_t> signal_row_sizes;\n\n    std::atomic<std::size_t> written_row_indices{0};\n    std::vector<pod5::SignalTableRowIndex> signal_row_indices;\n};\n\nclass read_split_signal_table_batch_rows {\npublic:\n    struct PatchRecord {\n        PatchRecord(\n            std::shared_ptr<states::read_read_table_rows_no_signal> dest_read_table,\n            std::uint64_t dest_batch_row_index)\n        : dest_read_table(dest_read_table)\n        , dest_batch_row_index(dest_batch_row_index)\n        {\n        }\n\n        std::shared_ptr<states::read_read_table_rows_no_signal> dest_read_table;\n        std::uint64_t dest_batch_row_index;\n    };\n\n    read_split_signal_table_batch_rows(\n        pod5::SignalBuilderVariant && signal_builder,\n        arrow::MemoryPool * pool)\n    : read_id_builder(pod5::make_read_id_builder(pool))\n    , signal_builder(std::move(signal_builder))\n    , samples_builder(pool)\n    {\n    }\n\n    std::unique_ptr<arrow::FixedSizeBinaryBuilder> read_id_builder;\n    pod5::SignalBuilderVariant signal_builder;\n    arrow::UInt32Builder samples_builder;\n\n    std::vector<PatchRecord> patch_rows;\n    bool final_batch = false;\n\n    std::size_t row_count() const { return patch_rows.size(); }\n};\n\nstruct finished {};\n\nusing shared_variant = std::variant<\n    std::shared_ptr<unread_read_table_rows>,\n    std::shared_ptr<read_split_signal_table_batch_rows>,\n    std::shared_ptr<read_read_table_rows_no_signal>,\n    std::shared_ptr<finished>>;\n\n}}  // namespace repack::states\n"
  },
  {
    "path": "c++/pod5_format_pybind/repack/repack_utils.h",
    "content": "#pragma once\n\n#include \"pod5_format/read_table_reader.h\"\n\n#include <arrow/array/array_dict.h>\n\n#include <mutex>\n#include <unordered_map>\n\nnamespace repack {\n\nstruct pair_hasher {\n    template <class T1, class T2>\n    std::size_t operator()(std::pair<T1, T2> const & pair) const\n    {\n        return std::hash<T1>{}(pair.first) ^ std::hash<T2>{}(pair.second);\n    }\n};\n\nstruct run_info_hasher {\n    std::size_t operator()(pod5::RunInfoData const & run_info) const\n    {\n        return std::hash<std::string>{}(run_info.acquisition_id);\n    }\n};\n\nclass ReadsTableDictionaryManager {\npublic:\n    ReadsTableDictionaryManager(\n        std::shared_ptr<pod5::FileWriter> const & output_file,\n        std::mutex & writer_mutex)\n    : m_output_file(output_file)\n    , m_writer_mutex(writer_mutex)\n    {\n    }\n\n    // Find or create a pore index in the output file - expects to run on strand.\n    arrow::Result<pod5::PoreDictionaryIndex> find_pore_index(\n        std::shared_ptr<pod5::FileReader> const & source_file,\n        pod5::ReadTableRecordBatch const & source_batch,\n        pod5::PoreDictionaryIndex source_index)\n    {\n        std::lock_guard<std::mutex> l(m_writer_mutex);\n\n        ARROW_ASSIGN_OR_RAISE(auto source_data, source_batch.get_pore_type(source_index));\n        pod5::PoreDictionaryIndex dest_index = 0;\n\n        // See if we have the same run info by value stored in the file:\n        auto data_lookup_it = m_pore_data_indexes.find(source_data);\n        if (data_lookup_it != m_pore_data_indexes.end()) {\n            dest_index = data_lookup_it->second;\n        } else {\n            ARROW_ASSIGN_OR_RAISE(dest_index, m_output_file->add_pore_type(source_data));\n        }\n\n        m_pore_data_indexes[source_data] = dest_index;\n        return dest_index;\n    }\n\n    // Find or create a run_info index in the output file - expects to run on strand.\n    arrow::Result<pod5::RunInfoDictionaryIndex> find_run_info_index(\n        std::shared_ptr<pod5::FileReader> const & source_file,\n        pod5::ReadTableRecordBatch const & source_batch,\n        pod5::RunInfoDictionaryIndex source_index)\n    {\n        std::lock_guard<std::mutex> l(m_writer_mutex);\n\n        ARROW_ASSIGN_OR_RAISE(auto source_data, source_batch.get_run_info(source_index));\n        ARROW_ASSIGN_OR_RAISE(auto const run_info, source_file->find_run_info(source_data));\n        pod5::RunInfoDictionaryIndex dest_index = 0;\n\n        // See if we have the same run info by value stored in the file:\n        auto data_lookup_it = m_run_info_data_indexes.find(*run_info);\n        if (data_lookup_it != m_run_info_data_indexes.end()) {\n            dest_index = data_lookup_it->second;\n        } else {\n            ARROW_ASSIGN_OR_RAISE(dest_index, m_output_file->add_run_info(*run_info));\n        }\n\n        m_run_info_data_indexes[*run_info] = dest_index;\n        return dest_index;\n    }\n\nprivate:\n    std::shared_ptr<pod5::FileWriter> m_output_file;\n\n    std::mutex & m_writer_mutex;\n\n    std::unordered_map<std::string, pod5::PoreDictionaryIndex> m_pore_data_indexes;\n    std::unordered_map<pod5::RunInfoData, pod5::RunInfoDictionaryIndex, run_info_hasher>\n        m_run_info_data_indexes;\n};\n\nclass ReadsTableDictionaryThreadCache {\npublic:\n    ReadsTableDictionaryThreadCache(std::shared_ptr<ReadsTableDictionaryManager> const & main_cache)\n    : m_main_cache(main_cache)\n    {\n    }\n\n    // Find or create a pore index in the output file - expects to run on strand.\n    arrow::Result<pod5::PoreDictionaryIndex> find_pore_index(\n        std::shared_ptr<pod5::FileReader> const & source_file,\n        pod5::ReadTableRecordBatch const & source_batch,\n        pod5::PoreDictionaryIndex source_index)\n    {\n        auto const key = std::make_pair(make_file_key(source_file), source_index);\n        auto const it = m_pore_indexes.find(key);\n        if (it != m_pore_indexes.end()) {\n            return it->second;\n        }\n\n        ARROW_ASSIGN_OR_RAISE(\n            auto dest_index,\n            m_main_cache->find_pore_index(source_file, source_batch, source_index));\n        m_pore_indexes[key] = dest_index;\n        return dest_index;\n    }\n\n    // Find or create a run_info index in the output file - expects to run on strand.\n    arrow::Result<pod5::RunInfoDictionaryIndex> find_run_info_index(\n        std::shared_ptr<pod5::FileReader> const & source_file,\n        pod5::ReadTableRecordBatch const & source_batch,\n        pod5::RunInfoDictionaryIndex source_index)\n    {\n        auto const key = std::make_pair(make_file_key(source_file), source_index);\n        auto const it = m_run_info_indexes.find(key);\n        if (it != m_run_info_indexes.end()) {\n            return it->second;\n        }\n\n        ARROW_ASSIGN_OR_RAISE(\n            auto dest_index,\n            m_main_cache->find_run_info_index(source_file, source_batch, source_index));\n        m_run_info_indexes[key] = dest_index;\n        return dest_index;\n    }\n\nprivate:\n    using FileKey = std::uint64_t;\n\n    FileKey make_file_key(std::shared_ptr<pod5::FileReader> const & file)\n    {\n        return reinterpret_cast<FileKey>(file.get());\n    }\n\n    template <typename IndexType>\n    using DictionaryLookup =\n        std::unordered_map<std::pair<FileKey, IndexType>, IndexType, pair_hasher>;\n\n    std::shared_ptr<ReadsTableDictionaryManager> m_main_cache;\n\n    DictionaryLookup<pod5::PoreDictionaryIndex> m_pore_indexes;\n    DictionaryLookup<pod5::RunInfoDictionaryIndex> m_run_info_indexes;\n};\n\n}  // namespace repack\n"
  },
  {
    "path": "c++/pod5_format_pybind/repack/repacker.cpp",
    "content": "#include \"repacker.h\"\n\n#include \"pod5_format/internal/tracing/tracing.h\"\n#include \"repack_output.h\"\n#include \"repack_states.h\"\n\nnamespace repack {\n\nnamespace {\n\nvoid repacker_add_reads_preconditions(\n    std::shared_ptr<Pod5Repacker> const & repacker,\n    std::shared_ptr<Pod5RepackerOutput> const & output,\n    Pod5FileReaderPtr const & input)\n{\n    if (output->repacker() != repacker) {\n        throw std::runtime_error(\"Invalid repacker output passed, created by another repacker\");\n    }\n\n    if (!input.reader) {\n        throw std::runtime_error(\"Invalid input passed to repacker, no reader\");\n    }\n}\n\n}  // namespace\n\nPod5Repacker::Pod5Repacker() : m_thread_pool{pod5::make_thread_pool(10)} {}\n\nPod5Repacker::~Pod5Repacker() { finish(); }\n\nvoid Pod5Repacker::finish()\n{\n    POD5_TRACE_FUNCTION();\n    for (auto & output : m_outputs) {\n        output->set_finished();\n    }\n\n    check_for_error();\n\n    m_thread_pool->stop_and_drain();\n\n    for (auto & output : m_outputs) {\n        m_reads_complete_deleted_outputs += output->reads_completed();\n    }\n    m_outputs.clear();\n}\n\nstd::shared_ptr<Pod5RepackerOutput> Pod5Repacker::add_output(\n    std::shared_ptr<pod5::FileWriter> const & output,\n    bool check_duplicate_read_ids)\n{\n    POD5_TRACE_FUNCTION();\n    auto repacker_output = std::make_shared<Pod5RepackerOutput>(\n        shared_from_this(), m_thread_pool, output, check_duplicate_read_ids);\n    m_outputs.push_back(repacker_output);\n    return repacker_output;\n}\n\nvoid Pod5Repacker::set_output_finished(std::shared_ptr<Pod5RepackerOutput> const & output)\n{\n    if (output->repacker() != shared_from_this()) {\n        throw std::runtime_error(\"Invalid repacker output passed, created by another repacker\");\n    }\n\n    output->set_finished();\n}\n\nvoid Pod5Repacker::add_all_reads_to_output(\n    std::shared_ptr<Pod5RepackerOutput> const & output,\n    Pod5FileReaderPtr const & input)\n{\n    POD5_TRACE_FUNCTION();\n    repacker_add_reads_preconditions(shared_from_this(), output, input);\n\n    for (std::size_t i = 0; i < input.reader->num_read_record_batches(); ++i) {\n        output->register_new_reads(input.reader, i);\n    }\n\n    register_submitted_reader(input.reader);\n}\n\nvoid Pod5Repacker::py_add_selected_reads_to_output(\n    std::shared_ptr<Pod5RepackerOutput> const & output,\n    Pod5FileReaderPtr const & input,\n    py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> && batch_counts,\n    py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> && all_batch_rows)\n{\n    repacker_add_reads_preconditions(shared_from_this(), output, input);\n\n    auto batch_counts_span = gsl::make_span(batch_counts.data(), batch_counts.size());\n    auto all_batch_rows_span = gsl::make_span(all_batch_rows.data(), all_batch_rows.size());\n\n    add_selected_reads_to_output(output, input.reader, batch_counts_span, all_batch_rows_span);\n}\n\nvoid Pod5Repacker::add_selected_reads_to_output(\n    std::shared_ptr<Pod5RepackerOutput> const & output,\n    std::shared_ptr<pod5::FileReader> const & input,\n    gsl::span<std::uint32_t const> batch_counts_span,\n    gsl::span<std::uint32_t const> all_batch_rows_span)\n{\n    POD5_TRACE_FUNCTION();\n\n    std::size_t current_start_point = 0;\n    for (std::size_t i = 0; i < input->num_read_record_batches(); ++i) {\n        std::vector<std::uint32_t> batch_rows;\n        auto const batch_rows_span =\n            all_batch_rows_span.subspan(current_start_point, batch_counts_span[i]);\n\n        // If this batch has no selected\n        if (batch_rows_span.empty()) {\n            continue;\n        }\n\n        batch_rows.insert(batch_rows.end(), batch_rows_span.begin(), batch_rows_span.end());\n        current_start_point += batch_counts_span[i];\n\n        output->register_new_reads(input, i, std::move(batch_rows));\n    }\n\n    register_submitted_reader(input);\n}\n\nvoid Pod5Repacker::check_for_error() const\n{\n    for (auto const & output : m_outputs) {\n        if (output->has_error()) {\n            throw std::runtime_error(output->error().ToString());\n        }\n    }\n}\n\nbool Pod5Repacker::is_complete() const\n{\n    POD5_TRACE_FUNCTION();\n    check_for_error();\n\n    for (auto const & output : m_outputs) {\n        if (!output->is_complete()) {\n            return false;\n        }\n    }\n\n    return true;\n}\n\nstd::size_t Pod5Repacker::reads_completed() const\n{\n    POD5_TRACE_FUNCTION();\n    check_for_error();\n\n    std::size_t reads_complete = 0;\n    for (auto const & output : m_outputs) {\n        reads_complete += output->reads_completed();\n    }\n\n    return reads_complete + m_reads_complete_deleted_outputs;\n}\n\n}  // namespace repack\n"
  },
  {
    "path": "c++/pod5_format_pybind/repack/repacker.h",
    "content": "#pragma once\n\n#include \"pod5_format_pybind/api.h\"\n\n#include <pybind11/pybind11.h>\n\n#include <memory>\n#include <set>\n#include <vector>\n\nnamespace repack {\n\nclass Pod5RepackerOutput;\n\nclass Pod5Repacker : public std::enable_shared_from_this<Pod5Repacker> {\npublic:\n    Pod5Repacker();\n    ~Pod5Repacker();\n\n    void finish();\n\n    std::shared_ptr<Pod5RepackerOutput> add_output(\n        std::shared_ptr<pod5::FileWriter> const & output,\n        bool check_duplicate_read_ids);\n    void set_output_finished(std::shared_ptr<Pod5RepackerOutput> const & output);\n\n    void add_all_reads_to_output(\n        std::shared_ptr<Pod5RepackerOutput> const & output,\n        Pod5FileReaderPtr const & input);\n\n    void py_add_selected_reads_to_output(\n        std::shared_ptr<Pod5RepackerOutput> const & output,\n        Pod5FileReaderPtr const & input,\n        py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> && batch_counts,\n        py::array_t<std::uint32_t, py::array::c_style | py::array::forcecast> && all_batch_rows);\n\n    void add_selected_reads_to_output(\n        std::shared_ptr<Pod5RepackerOutput> const & output,\n        std::shared_ptr<pod5::FileReader> const & input,\n        gsl::span<std::uint32_t const> batch_counts,\n        gsl::span<std::uint32_t const> all_batch_rows);\n\n    bool is_complete() const;\n    std::size_t reads_completed() const;\n\n    std::size_t currently_open_file_reader_count()\n    {\n        check_for_error();\n        cleanup_submitted_readers();\n        return m_file_readers.size();\n    }\n\nprivate:\n    void check_for_error() const;\n\n    void cleanup_submitted_readers()\n    {\n        std::erase_if(m_file_readers, [](auto const & ptr) { return ptr.expired(); });\n    }\n\n    void register_submitted_reader(std::shared_ptr<pod5::FileReader> const & input)\n    {\n        cleanup_submitted_readers();\n        m_file_readers.insert(input);\n    }\n\n    std::shared_ptr<pod5::ThreadPool> m_thread_pool;\n    std::set<std::weak_ptr<pod5::FileReader>, std::owner_less<>> m_file_readers;\n    std::vector<std::shared_ptr<Pod5RepackerOutput>> m_outputs;\n\n    std::size_t m_reads_complete_deleted_outputs{0};\n};\n\n}  // namespace repack\n"
  },
  {
    "path": "c++/pod5_format_pybind/subset.cpp",
    "content": "#include \"subset.h\"\n\n#include <algorithm>\n#include <chrono>\n#include <cmath>\n#include <cstddef>\n#include <cstdlib>\n#include <limits>\n#include <ostream>\n#include <string>\n\n#ifndef _WIN32\n#include <sys/resource.h>\n#include <unistd.h>\n#else\n#include <io.h>\n\n#include <cstdio>  // _getmaxstdio\n#endif\n\n#include \"pod5_format/file_reader.h\"\n#include \"pod5_format/file_writer.h\"\n#include \"pod5_format/schema_metadata.h\"\n#include \"repack/repacker.h\"\n\n#include <iostream>\n\nnamespace io_limits {\n\n// Balance the number of open inputs by the output-side handle usage.\n// Prefer outputs over inputs to reduce the number of output\n// batches which iterate over all inputs.\nconstexpr std::float_t kOutputsBias = 0.7f;\nconstexpr std::size_t kMinHandles = 1;\nconstexpr std::size_t kBaseReserve = 16;\n\nstd::size_t clamp_open_inputs(std::size_t soft_limit, std::size_t output_files)\n{\n    constexpr std::size_t kMaxInHandles = 256;\n    std::size_t const reserve = kBaseReserve + output_files;\n    if (soft_limit <= reserve + kMinHandles) {\n        return kMinHandles;\n    }\n    return std::clamp(soft_limit - reserve, kMinHandles, kMaxInHandles);\n}\n\nstd::size_t clamp_open_outputs(std::size_t soft_limit)\n{\n    constexpr std::size_t kMaxOutHandles = 4096;\n    std::size_t const reserve = kBaseReserve + kMinHandles;\n    if (soft_limit <= reserve + kMinHandles) {\n        return kMinHandles;\n    }\n    std::size_t soft_upper = (std::size_t)(soft_limit * kOutputsBias);\n    if (soft_upper > 32) {\n        soft_upper = (soft_upper / 16) * 16;\n    }\n\n    return std::clamp(std::min(soft_limit - reserve, soft_upper), kMinHandles, kMaxOutHandles);\n}\n\nstd::size_t detect_soft_limit()\n{\n    //\n    constexpr std::size_t kSoftLimitFallback = 1024;\n\n#ifndef _WIN32\n    // Attempt to get the resource limits (if any)\n    struct rlimit rl{};\n    if (getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_cur != RLIM_INFINITY) {\n        return static_cast<std::size_t>(rl.rlim_cur);\n    }\n    long sc = sysconf(_SC_OPEN_MAX);\n    return sc > 0 ? static_cast<std::size_t>(sc) : kSoftLimitFallback;\n#else\n    // Only stdio stream limit, not a true OS handle limit.\n    int n = _getmaxstdio();\n    return n > 0 ? static_cast<std::size_t>(n) : kSoftLimitFallback;\n#endif\n}\n}  // namespace io_limits\n\n/// \\brief Simple progress bar for console applications\nclass ProgressBar {\npublic:\n    static constexpr int PB_WIDTH = 60;\n\n    ProgressBar() {}\n\n    ~ProgressBar() { std::fputs(\"\\n\", stdout); }\n\n    void set_task(std::string const & task_name)\n    {\n        m_task = task_name;\n        print_progress();\n    }\n\n    void update_max_steps(std::size_t max_steps) { this->m_max_steps = max_steps; }\n\n    void update(std::size_t current_step)\n    {\n        if (current_step == m_current_step) {\n            return;\n        }\n        m_current_step = current_step;\n        print_progress();\n    }\n\n    void print_progress()\n    {\n        float complete_ratio = static_cast<float>(m_current_step) / static_cast<float>(m_max_steps);\n        int complete_length = static_cast<int>(complete_ratio * PB_WIDTH);\n        std::string complete_string{\"\\r[\"};\n        for (int i = 0; i < PB_WIDTH; ++i) {\n            if (i < complete_length) {\n                complete_string += \"=\";\n            } else {\n                complete_string += \" \";\n            }\n        }\n        complete_string += \"] (\" + std::to_string(m_current_step) + \"/\"\n                           + std::to_string(m_max_steps) + \") \" + m_task;\n        m_max_printed_width = std::max<std::size_t>(m_max_printed_width, complete_string.size());\n        // Pad to max width to overwrite previous longer lines\n        complete_string.resize(m_max_printed_width, ' ');\n        std::cout << complete_string.c_str() << std::flush;\n    }\n\nprivate:\n    std::string m_task;\n    std::size_t m_max_steps{0};\n    std::size_t m_current_step{0};\n    std::size_t m_max_printed_width{0};\n};\n\nvoid subset_pod5s_with_mapping(\n    std::vector<std::filesystem::path> inputs,\n    std::filesystem::path output,\n    std::map<std::string, std::vector<std::string>> read_id_to_dest,\n    bool missing_ok,\n    bool duplicate_ok,\n    bool force_overwrite)\n{\n    auto next_interrupt_check = std::chrono::steady_clock::now();\n    auto poll_python_interrupt = [&]() {\n        auto const now = std::chrono::steady_clock::now();\n        if (now < next_interrupt_check) {\n            return;\n        }\n        next_interrupt_check = now + std::chrono::milliseconds(500);\n\n        pybind11::gil_scoped_acquire gil;\n        if (PyErr_CheckSignals() != 0) {\n            throw pybind11::error_already_set();\n        }\n    };\n\n    struct OutputInfo {\n        OutputInfo(std::shared_ptr<repack::Pod5RepackerOutput> && repacker_output_)\n        : repacker_output(std::move(repacker_output_))\n        {\n        }\n\n        std::shared_ptr<repack::Pod5RepackerOutput> repacker_output;\n\n        void clear_per_input_working_data()\n        {\n            batch_counts.clear();\n            all_batch_rows.clear();\n            batch_counts.reserve(32);\n            all_batch_rows.reserve(128);\n        }\n\n        void add_row(std::uint32_t row_index)\n        {\n            all_batch_rows.push_back(row_index);\n            next_batch_size += 1;\n        }\n\n        void finish_batch()\n        {\n            batch_counts.push_back(next_batch_size);\n            next_batch_size = 0;\n        }\n\n        // Per file working vectors:\n        std::uint32_t next_batch_size = 0;\n        std::vector<std::uint32_t> batch_counts;\n        std::vector<std::uint32_t> all_batch_rows;\n    };\n\n    pod5::FileWriterOptions output_options{};\n    output_options.set_keep_signal_file_open(false);\n    output_options.set_keep_read_table_file_open(false);\n    output_options.set_keep_run_info_file_open(false);\n    pod5::FileReaderOptions input_options{};\n    input_options.set_force_disable_file_mapping(true);\n\n    // Process inputs in deterministic lexical path order.\n    std::sort(inputs.begin(), inputs.end());\n\n    std::vector<std::filesystem::path> created_output_files;\n    auto cleanup = gsl::finally([&]() {\n        for (auto const & path : created_output_files) {\n            std::error_code ec;\n            std::filesystem::remove(path, ec);\n        }\n    });\n\n    bool issued_migration_warning = false;\n    std::size_t const io_soft_limit = io_limits::detect_soft_limit();\n    std::size_t const max_out_size = io_limits::clamp_open_outputs(io_soft_limit);\n\n    // Create indexable view of the map iterators so we can conveniently index in batches.\n    std::vector<std::map<std::string, std::vector<std::string>>::const_iterator> read_id_dest_iters;\n    read_id_dest_iters.reserve(read_id_to_dest.size());\n    std::size_t total_requested_read_ids = 0;\n    for (auto it = read_id_to_dest.begin(); it != read_id_to_dest.end(); ++it) {\n        poll_python_interrupt();\n\n        // Check we're not unintentionally overwriting files\n        auto const output_path = output / it->first;\n        if (std::filesystem::exists(output_path)) {\n            if (!force_overwrite) {\n                throw std::runtime_error(\n                    \"Output files already exists and --force-overwrite not set. \");\n            } else {\n                std::filesystem::remove(output_path);\n            }\n        }\n\n        // Index the map iterator and tally total reads\n        read_id_dest_iters.push_back(it);\n        total_requested_read_ids += it->second.size();\n    }\n\n    std::size_t found_read_count = 0;\n    std::size_t total_reads_completed = 0;\n\n    std::size_t const total_output_batches =\n        (read_id_dest_iters.size() + max_out_size - 1) / max_out_size;\n\n    if (total_output_batches > 1) {\n        std::cerr << \"Subsetting inputs into \" << std::to_string(read_id_dest_iters.size())\n                  << \" files in \" << std::to_string(total_output_batches) << \" batches of at most \"\n                  << max_out_size << \" outputs. IO limit: \" << std::to_string(io_soft_limit)\n                  << std::endl;\n    }\n\n    ProgressBar progress_bar;\n    progress_bar.set_task(\"Starting...\");\n    progress_bar.update_max_steps(total_requested_read_ids);\n\n    // Iterate over outputs in batches\n    for (std::size_t out_st = 0; out_st < read_id_dest_iters.size(); out_st += max_out_size) {\n        poll_python_interrupt();\n        std::size_t const output_batch_index = (out_st / max_out_size) + 1;\n        std::size_t const out_end = std::min(out_st + max_out_size, read_id_dest_iters.size());\n        std::string const batch_prefix = \"Batch [\" + std::to_string(output_batch_index) + \"/\"\n                                         + std::to_string(total_output_batches) + \"]: \";\n\n        auto repacker = std::make_shared<repack::Pod5Repacker>();\n        std::unordered_multimap<pod5::Uuid, std::uint32_t> read_id_lookup;\n        std::vector<OutputInfo> dest_to_output;\n        dest_to_output.reserve(out_end - out_st);\n\n        // For each output in this batch\n        for (std::size_t out_idx = out_st; out_idx < out_end; ++out_idx) {\n            poll_python_interrupt();\n            auto const & read_id_dest = *read_id_dest_iters[out_idx];\n            auto const output_path = output / read_id_dest.first;\n\n            // Create the output file\n            auto writer =\n                pod5::create_file_writer(output_path.string(), \"pod5_subset\", output_options);\n            if (!writer.ok()) {\n                std::cerr << \"Failed to create output file: \" << output_path << std::endl;\n                throw std::runtime_error(\"Failed to create output POD5 file\");\n            }\n\n            // Add the output file writer to the repacker\n            created_output_files.push_back(output_path);\n            auto repacker_output_file = repacker->add_output(std::move(*writer), !duplicate_ok);\n            std::size_t const repacker_output_idx = dest_to_output.size();\n            dest_to_output.emplace_back(std::move(repacker_output_file));\n\n            // Associate the requested read_ids to this output\n            for (auto const & read_id : read_id_dest.second) {\n                auto read_id_uuid = pod5::Uuid::from_string(read_id);\n                if (!read_id_uuid) {\n                    std::cerr << \"Invalid read id uuid: \" << read_id << std::endl;\n                    throw std::runtime_error(\"Invalid read id uuid in mapping\");\n                }\n                read_id_lookup.insert(std::make_pair(*read_id_uuid, repacker_output_idx));\n            }\n        }\n\n        // Scale the max open input files by current output handle usage and system limits.\n        std::size_t const max_open_input_files =\n            io_limits::clamp_open_inputs(io_soft_limit, dest_to_output.size());\n        std::size_t const max_in_size = std::max<std::size_t>(1, max_open_input_files);\n\n        // Wait for the number of open readers in the repacker to go below `limit`\n        auto wait_for_open_readers_below = [&](std::size_t limit) {\n            auto last_update = std::chrono::steady_clock::now();\n            while (repacker->currently_open_file_reader_count() >= limit) {\n                std::this_thread::sleep_for(std::chrono::milliseconds(100));\n                poll_python_interrupt();\n\n                auto const now = std::chrono::steady_clock::now();\n                if (now - last_update >= std::chrono::milliseconds(2000)) {\n                    progress_bar.update(total_reads_completed + repacker->reads_completed());\n                    progress_bar.set_task(\n                        batch_prefix + \"Waiting for queued writes to complete from \"\n                        + std::to_string(repacker->currently_open_file_reader_count())\n                        + \"files...\");\n                    last_update = now;\n                }\n            }\n        };\n\n        // Wait for the repacker to finish with it's currently open readers\n        auto wait_for_open_readers_zero = [&]() {\n            auto last_update = std::chrono::steady_clock::now();\n            while (repacker->currently_open_file_reader_count() > 0) {\n                std::this_thread::sleep_for(std::chrono::milliseconds(100));\n                poll_python_interrupt();\n\n                auto const now = std::chrono::steady_clock::now();\n                if (now - last_update >= std::chrono::milliseconds(2000)) {\n                    progress_bar.update(total_reads_completed + repacker->reads_completed());\n                    progress_bar.set_task(batch_prefix + \"Waiting for batch IO to complete...\");\n                    last_update = now;\n                }\n            }\n        };\n\n        // Walk each input file in chunks for this output batch.\n        for (std::size_t in_st = 0; in_st < inputs.size(); in_st += max_in_size) {\n            poll_python_interrupt();\n            std::size_t const in_end = std::min(in_st + max_in_size, inputs.size());\n\n            // Add an input in this chunk\n            for (std::size_t in_idx = in_st; in_idx < in_end; ++in_idx) {\n                poll_python_interrupt();\n                auto const & input_path = inputs[in_idx];\n\n                // Keep in-flight readers below chunk limit.\n                wait_for_open_readers_below(max_in_size);\n\n                // Clear previous row selections from a previous input file.\n                for (auto & output_file : dest_to_output) {\n                    output_file.clear_per_input_working_data();\n                }\n\n                // \"Batch [i/N]: Subsetting {input}\"\n                progress_bar.set_task(\n                    batch_prefix + \"Subsetting \" + input_path.filename().string());\n\n                // Open the input file\n                auto input_reader_opt = pod5::open_file_reader(input_path.string(), input_options);\n                if (!input_reader_opt.ok()) {\n                    std::cerr << \"Failed to open input file: \" << input_path << std::endl;\n                    throw std::runtime_error(\"Failed to open input POD5 file\");\n                }\n                auto const & input_reader = *input_reader_opt;\n                if (!issued_migration_warning && out_st == 0) {\n                    auto const pre_migration_version = input_reader->file_version_pre_migration();\n                    auto const post_migration_version =\n                        input_reader->schema_metadata().writing_pod5_version;\n                    if (pre_migration_version != post_migration_version) {\n                        std::cerr << \"Warning: Migrated an input from POD5 version \"\n                                  << pre_migration_version.to_string() << \" to \"\n                                  << post_migration_version.to_string()\n                                  << \" while subsetting. This can affect performance \"\n                                     \"significantly. Consider updating input files.\"\n                                  << std::endl;\n                    }\n                    issued_migration_warning = true;\n                }\n\n                // Walk the input file batches:\n                for (std::size_t i = 0; i < input_reader->num_read_record_batches(); ++i) {\n                    poll_python_interrupt();\n                    auto batch = input_reader->read_read_record_batch(i);\n                    if (!batch.ok()) {\n                        std::cerr << \"Failed to read batch \" << i\n                                  << \" from input file: \" << input_path << std::endl;\n                        throw std::runtime_error(\"Failed to read read record batch from POD5 file\");\n                    }\n\n                    // Test each read id in the batch to see if we want it:\n                    auto const & read_id_column = batch->read_id_column();\n                    for (std::int64_t row = 0; row < read_id_column->length(); ++row) {\n                        if ((row & 0x3FF) == 0) {\n                            poll_python_interrupt();\n                        }\n                        auto const found = read_id_lookup.equal_range(read_id_column->Value(row));\n                        for (auto it = found.first; it != found.second; ++it) {\n                            dest_to_output[it->second].add_row(row);\n                            found_read_count += 1;\n                        }\n                    }\n\n                    // Store how many rows in this batch were selected:\n                    for (auto & output_file : dest_to_output) {\n                        output_file.finish_batch();\n                    }\n\n                    progress_bar.update(total_reads_completed + repacker->reads_completed());\n                }\n\n                // Submit selected reads to each output:\n                for (auto & output_file : dest_to_output) {\n                    repacker->add_selected_reads_to_output(\n                        output_file.repacker_output,\n                        input_reader,\n                        gsl::make_span(output_file.batch_counts),\n                        gsl::make_span(output_file.all_batch_rows));\n                }\n            }\n\n            // Batch drain barrier for inputs in this output batch.\n            wait_for_open_readers_zero();\n        }\n\n        // Set this output batch to finished:\n        std::thread finisher([&] {\n            for (auto & output_file : dest_to_output) {\n                repacker->set_output_finished(output_file.repacker_output);\n            }\n        });\n        auto join_finisher = gsl::finally([&] {\n            if (finisher.joinable()) {\n                finisher.join();\n            }\n        });\n\n        // Wait for this batch to complete:\n        progress_bar.set_task(batch_prefix + \"Waiting for batch IO to complete...\");\n        try {\n            while (!repacker->is_complete()) {\n                std::this_thread::sleep_for(std::chrono::milliseconds(100));\n                poll_python_interrupt();\n                progress_bar.update(total_reads_completed + repacker->reads_completed());\n            }\n        } catch (pybind11::error_already_set const &) {\n            throw;\n        } catch (std::exception const & e) {\n            std::cout << \"\\nError during repacking: \" << e.what() << std::endl;\n        }\n\n        if (finisher.joinable()) {\n            finisher.join();\n        }\n\n        repacker->finish();\n        total_reads_completed += repacker->reads_completed();\n    }\n    progress_bar.set_task(\"Finished\");\n\n    if (found_read_count < total_requested_read_ids && !missing_ok) {\n        throw std::runtime_error(\"Missing read_ids from inputs but --missing-ok not set\");\n    }\n\n    // Clear created output files from cleanup since we succeeded:\n    created_output_files.clear();\n}\n"
  },
  {
    "path": "c++/pod5_format_pybind/subset.h",
    "content": "#include <pybind11/stl/filesystem.h>\n\n#include <filesystem>\n#include <map>\n#include <string>\n#include <vector>\n\nvoid subset_pod5s_with_mapping(\n    std::vector<std::filesystem::path> inputs,\n    std::filesystem::path output,\n    std::map<std::string, std::vector<std::string>> read_id_to_dest,\n    bool missing_ok,\n    bool duplicate_ok,\n    bool force_overwrite);\n"
  },
  {
    "path": "c++/pod5_format_pybind/utils.h",
    "content": "#pragma once\n\n#include \"pod5_format/result.h\"\n\ninline void raise_error(arrow::Status const & status)\n{\n    throw std::runtime_error(status.ToString());\n}\n\ntemplate <typename T>\ninline void raise_error(arrow::Result<T> const & result)\n{\n    throw std::runtime_error(result.status().ToString());\n}\n\n#define POD5_PYTHON_RETURN_NOT_OK(statement) \\\n    do {                                     \\\n        auto const _res = (statement);       \\\n        if (!_res.ok()) {                    \\\n            raise_error(_res);               \\\n        }                                    \\\n    } while (false)\n\n#define POD5_PYTHON_ASSIGN_OR_RAISE_IMPL(result_name, lhs, rexpr) \\\n    auto && result_name = (rexpr);                                \\\n    if (!(result_name).ok()) {                                    \\\n        raise_error(result_name);                                 \\\n    }                                                             \\\n    lhs = std::move(result_name).ValueUnsafe();\n\n#define POD5_PYTHON_ASSIGN_OR_RAISE(lhs, rexpr) \\\n    POD5_PYTHON_ASSIGN_OR_RAISE_IMPL(           \\\n        ARROW_ASSIGN_OR_RAISE_NAME(_error_or_value, __COUNTER__), lhs, rexpr);\n\ninline void throw_on_error(pod5::Status const & s)\n{\n    if (!s.ok()) {\n        throw std::runtime_error(s.ToString());\n    }\n}\n\ntemplate <typename T>\ninline T throw_on_error(pod5::Result<T> const & s)\n{\n    if (!s.ok()) {\n        throw std::runtime_error(s.status().ToString());\n    }\n    return *s;\n}\n"
  },
  {
    "path": "c++/test/CMakeLists.txt",
    "content": "\nadd_executable(pod5_unit_tests\n    main.cpp\n    c_api_null_input.cpp\n    c_api_test_utils.h\n    c_api_tests.cpp\n    c_api_build_test.c\n    file_reader_writer_tests.cpp\n    output_stream_tests.cpp\n    read_table_writer_utils_tests.cpp\n    read_table_tests.cpp\n    run_info_table_tests.cpp\n    schema_tests.cpp\n    signal_compression_tests.cpp\n    signal_table_tests.cpp\n    svb16_scalar_tests.cpp\n    svb16_x64_tests.cpp\n    test_utils.h\n    thread_pool_tests.cpp\n    utils.h\n    uuid_tests.cpp\n)\n\nif (${CMAKE_CXX_COMPILER_ID} MATCHES \"Clang\")\n    set_source_files_properties(c_api_build_test.c PROPERTIES COMPILE_OPTIONS \"-Wdocumentation\")\nendif()\n\ntarget_link_libraries(pod5_unit_tests\n    PUBLIC\n        pod5_format\n        ${maybe_public_libs}\n)\n\nset_property(TARGET pod5_unit_tests PROPERTY CXX_STANDARD 20)\nif (NOT MSVC)\n    target_compile_options(pod5_unit_tests PRIVATE ${pod5_warning_options})\nendif()\n\nadd_test(\n    NAME pod5_unit_tests\n    COMMAND pod5_unit_tests\n)\n"
  },
  {
    "path": "c++/test/TemporaryDirectory.h",
    "content": "#pragma once\n\n#include \"pod5_format/uuid.h\"\n\n#include <filesystem>\n\nnamespace ont { namespace testutils {\n\nstatic std::string make_unique_name()\n{\n    std::random_device gen;\n    auto uuid_gen = pod5::BasicUuidRandomGenerator<std::random_device>{gen};\n    return to_string(uuid_gen());\n}\n\n/// A scoped directory with a fixed name.\nclass TemporaryDirectory {\npublic:\n    /// Where to create the directory.\n    enum class Location { CurrentDir, TempDir };\n    enum class DeleteBehaviour { AfterOnly, BeforeAndAfter };\n\n    /// Creates a random temporary directory\n    TemporaryDirectory()\n    : TemporaryDirectory(make_unique_name(), Location::TempDir, DeleteBehaviour::AfterOnly)\n    {\n    }\n\n    /// Create a directory.\n    explicit TemporaryDirectory(std::filesystem::path path, DeleteBehaviour delete_behaviour)\n    : TemporaryDirectory(std::move(path), Location::CurrentDir, delete_behaviour)\n    {\n    }\n\n    /// Create a directory.\n    explicit TemporaryDirectory(\n        std::filesystem::path path,\n        Location location = Location::CurrentDir,\n        DeleteBehaviour delete_behaviour = DeleteBehaviour::AfterOnly)\n    {\n        if (!path.is_absolute()) {\n            if (location == Location::CurrentDir) {\n                path = std::filesystem::absolute(path);\n            } else {\n                path = std::filesystem::temp_directory_path() / path;\n            }\n        }\n        if (delete_behaviour == DeleteBehaviour::BeforeAndAfter) {\n            std::filesystem::remove_all(m_path);\n        }\n        std::filesystem::create_directories(path);\n        m_path = path;\n    }\n\n    TemporaryDirectory(TemporaryDirectory const &) = delete;\n    TemporaryDirectory & operator=(TemporaryDirectory const &) = delete;\n\n    TemporaryDirectory(TemporaryDirectory &&) = default;\n    TemporaryDirectory & operator=(TemporaryDirectory &&) = default;\n\n    /// Remove the referenced directory.\n    ///\n    /// Does nothing if this is not a valid object.\n    ~TemporaryDirectory()\n    {\n        if (!m_path.empty()) {\n            std::error_code error;\n            std::filesystem::remove_all(m_path, error);\n        }\n    }\n\n    /// Path to the directory.\n    std::filesystem::path const & path() const { return m_path; }\n\n    explicit operator bool() const { return !m_path.empty(); }\n\nprivate:\n    std::filesystem::path m_path;\n};\n\ntemplate <class CharType, class CharTrait>\nstd::basic_ostream<CharType, CharTrait> & operator<<(\n    std::basic_ostream<CharType, CharTrait> & os,\n    TemporaryDirectory const & td)\n{\n    return os << \"TemporaryDirectory{ \" << td.path() << \" }\";\n}\n\n}}  // namespace ont::testutils\n"
  },
  {
    "path": "c++/test/c_api_build_test.c",
    "content": "#include \"pod5_format/c_api.h\"\n\n// Build check to verify a c file can include the c_api\n"
  },
  {
    "path": "c++/test/c_api_null_input.cpp",
    "content": "#include \"c_api_test_utils.h\"\n#include \"pod5_format/c_api.h\"\n#include \"utils.h\"\n\n#include <bit>\n#include <numeric>\n#include <string_view>\n\nnamespace {\n\nvoid pod5_reset_error()\n{\n    pod5_vbz_compressed_signal_max_size(1);\n    REQUIRE_POD5_OK(pod5_get_error_no());\n    REQUIRE(pod5_get_error_string() == std::string_view{});\n}\n\nnamespace detail {\n\ntemplate <std::size_t PtrIdx, typename... Args>\nconstexpr std::size_t ptr_idx_to_arg_idx()\n{\n    // Count how many pointers we've seen at each arg.\n    std::size_t ptr_count[]{static_cast<std::size_t>(std::is_pointer_v<Args>)...};\n    std::partial_sum(std::begin(ptr_count), std::end(ptr_count), std::begin(ptr_count));\n\n    // Find which arg matches our index.\n    for (std::size_t arg_i = 0; arg_i < std::size(ptr_count); arg_i++) {\n        if (ptr_count[arg_i] == PtrIdx + 1) {\n            return arg_i;\n        }\n    }\n\n    throw \"Cannot find arg for ptr\";\n}\n\ntemplate <std::size_t PtrIdx, typename... Args>\nvoid make_ptr_null_impl(std::tuple<Args...> & args, std::uint64_t valid_ptr_bitset)\n{\n    // Grab the arg that we'll be modifying.\n    constexpr std::size_t ArgIdx = ptr_idx_to_arg_idx<PtrIdx, Args...>();\n    auto & arg = std::get<ArgIdx>(args);\n    using ArgT = std::remove_reference_t<decltype(arg)>;\n    static_assert(std::is_pointer_v<ArgT>);\n\n    // If the arg isn't a valid one then replace it with a nullptr.\n    auto const valid = (valid_ptr_bitset >> PtrIdx) & 1;\n    if (!valid) {\n        arg = nullptr;\n    }\n}\n\ntemplate <typename... Args, std::size_t... PtrIdxs>\nvoid make_ptrs_null(\n    std::tuple<Args...> & args,\n    std::uint64_t valid_ptr_bitset,\n    std::index_sequence<PtrIdxs...>)\n{\n    (make_ptr_null_impl<PtrIdxs>(args, valid_ptr_bitset), ...);\n}\n\ntemplate <typename... Args, std::size_t... ArgIdxs>\nauto unpack_and_call(\n    pod5_error_t (*func)(Args...),\n    std::tuple<Args...> args,\n    std::index_sequence<ArgIdxs...>)\n{\n    return func(std::get<ArgIdxs>(args)...);\n}\n\n}  // namespace detail\n\ntemplate <typename... Args>\nvoid call_with_nulls(pod5_error_t (*func)(Args...), Args... args)\n{\n    auto const valid_inputs = std::make_tuple(args...);\n\n    constexpr std::size_t num_args = sizeof...(Args);\n    static_assert(num_args <= 64, \"uint64_t isn't big enough for a bitmask\");\n    constexpr std::size_t num_pointers = (std::is_pointer_v<Args> + ...);\n\n    constexpr auto ArgIdxs = std::make_index_sequence<num_args>();\n    constexpr auto PtrIdxs = std::make_index_sequence<num_pointers>();\n\n    // Try every combination of NULL for the pointers.\n    for (std::uint64_t valid_ptr_bitset = 0; std::popcount(valid_ptr_bitset) != num_pointers;\n         valid_ptr_bitset++)\n    {\n        CAPTURE(valid_ptr_bitset);\n\n        // Replace some args with nulls.\n        auto inputs = valid_inputs;\n        detail::make_ptrs_null(inputs, valid_ptr_bitset, PtrIdxs);\n\n        // Make the call.\n        pod5_reset_error();\n        pod5_error_t const result = detail::unpack_and_call(func, inputs, ArgIdxs);\n\n        // Check that it was an error.\n        // TODO: We could improve this to check that the first invalid arg matches the error that's\n        // reported (ie null string, null file, etc...), but this is already overengineered enough.\n        //int const first_ptr = std::countr_zero(~valid_ptr_bitset); // codespell:ignore\n        CHECK_POD5_NOT_OK(result);\n        CHECK_THAT(pod5_get_error_string(), Catch::Matchers::Contains(\"null\"));\n    }\n}\n\nTEST_CASE(\"NULL input doesn't crash\")\n{\n    using Catch::Matchers::Contains;\n\n    pod5_init();\n    auto cleanup = gsl::finally([] { pod5_terminate(); });\n\n    // Make a temporary file for the read API to use.\n    static constexpr char const temporary_filename[] = \"./foo_c_api.pod5\";\n    {\n        REQUIRE(remove_file_if_exists(temporary_filename).ok());\n        Pod5FileWriter_t * writer = pod5_create_file(temporary_filename, \"c_software\", nullptr);\n        REQUIRE_POD5_OK(pod5_get_error_no());\n        REQUIRE(writer);\n\n        std::int16_t pore_type_id{};\n        REQUIRE_POD5_OK(pod5_add_pore(&pore_type_id, writer, \"pore_type\"));\n\n        std::int16_t run_info_id{};\n        size_t const num_kv_pairs = 1;\n        char const * keys[]{\"key\"};\n        char const * values[]{\"value\"};\n        REQUIRE_POD5_OK(pod5_add_run_info(\n            &run_info_id,\n            writer,\n            \"acquisition_id\",\n            1,\n            1,\n            -1,\n            num_kv_pairs,\n            keys,\n            values,\n            \"experiment_name\",\n            \"flow_cell_id\",\n            \"flow_cell_product_code\",\n            \"protocol_name\",\n            \"protocol_run_id\",\n            1,\n            \"sample_id\",\n            1,\n            \"sequencing_kit\",\n            \"sequencer_position\",\n            \"sequencer_position_type\",\n            \"software\",\n            \"system_name\",\n            \"system_type\",\n            num_kv_pairs,\n            keys,\n            values));\n\n        read_id_t const read_id{};\n        uint32_t const read_number{};\n        uint64_t const start_sample{};\n        float const median_before{};\n        uint16_t const channel{};\n        uint8_t const well{};\n        float const calibration_offset{};\n        float const calibration_scale{};\n        pod5_end_reason_t const end_reason{};\n        uint8_t const end_reason_forced{};\n        uint64_t const num_minknow_events{};\n        float const tracked_scaling_scale{};\n        float const tracked_scaling_shift{};\n        float const predicted_scaling_scale{};\n        float const predicted_scaling_shift{};\n        uint32_t const num_reads_since_mux_change{};\n        float const time_since_mux_change{};\n        float const open_pore_level{};\n\n        ReadBatchRowInfoArrayV3 const row_data_v3{\n            &read_id,\n            &read_number,\n            &start_sample,\n            &median_before,\n            &channel,\n            &well,\n            &pore_type_id,\n            &calibration_offset,\n            &calibration_scale,\n            &end_reason,\n            &end_reason_forced,\n            &run_info_id,\n            &num_minknow_events,\n            &tracked_scaling_scale,\n            &tracked_scaling_shift,\n            &predicted_scaling_scale,\n            &predicted_scaling_shift,\n            &num_reads_since_mux_change,\n            &time_since_mux_change};\n\n        int16_t const signal_data[]{1, 2, 3, 4, 5};\n        uint32_t const signal_size = std::size(signal_data);\n        auto * signal_data_ptr = signal_data;\n\n        REQUIRE_POD5_OK(pod5_add_reads_data(\n            writer,\n            1,\n            READ_BATCH_ROW_INFO_VERSION_3,\n            &row_data_v3,\n            &signal_data_ptr,\n            &signal_size));\n\n        ReadBatchRowInfoArrayV4 const row_data_v4{\n            &read_id,\n            &read_number,\n            &start_sample,\n            &median_before,\n            &channel,\n            &well,\n            &pore_type_id,\n            &calibration_offset,\n            &calibration_scale,\n            &end_reason,\n            &end_reason_forced,\n            &run_info_id,\n            &num_minknow_events,\n            &tracked_scaling_scale,\n            &tracked_scaling_shift,\n            &predicted_scaling_scale,\n            &predicted_scaling_shift,\n            &num_reads_since_mux_change,\n            &time_since_mux_change,\n            &open_pore_level};\n\n        REQUIRE_POD5_OK(pod5_add_reads_data(\n            writer,\n            1,\n            READ_BATCH_ROW_INFO_VERSION_4,\n            &row_data_v4,\n            &signal_data_ptr,\n            &signal_size));\n\n        REQUIRE_POD5_OK(pod5_close_and_free_writer(writer));\n    }\n\n    SECTION(\"Reader API\")\n    {\n        {\n            INFO(\"pod5_open_file\")\n\n            pod5_reset_error();\n            CHECK(pod5_open_file(nullptr) == nullptr);\n            CHECK_THAT(pod5_get_error_string(), Contains(\"null string passed\"));\n        }\n\n        {\n            INFO(\"pod5_open_file_options\")\n\n            Pod5ReaderOptions_t options{};\n\n            pod5_reset_error();\n            CHECK(pod5_open_file_options(nullptr, nullptr) == nullptr);\n            CHECK_THAT(pod5_get_error_string(), Contains(\"null string passed\"));\n\n            pod5_reset_error();\n            CHECK(pod5_open_file_options(temporary_filename, nullptr) == nullptr);\n            CHECK_THAT(pod5_get_error_string(), Contains(\"null passed\"));\n\n            pod5_reset_error();\n            CHECK(pod5_open_file_options(nullptr, &options) == nullptr);\n            CHECK_THAT(pod5_get_error_string(), Contains(\"null string passed\"));\n        }\n\n        {\n            INFO(\"pod5_close_and_free_reader\")\n\n            pod5_reset_error();\n            CHECK_POD5_OK(pod5_close_and_free_reader(nullptr));\n        }\n\n        // The rest of these functions require a reader.\n        Pod5FileReader_t * mutable_reader = pod5_open_file(temporary_filename);\n        REQUIRE(mutable_reader);\n        auto close_reader =\n            gsl::finally([&mutable_reader] { pod5_close_and_free_reader(mutable_reader); });\n        Pod5FileReader_t const * reader = mutable_reader;\n\n        {\n            INFO(\"pod5_get_file_info\")\n\n            FileInfo file_info{};\n            call_with_nulls(pod5_get_file_info, reader, &file_info);\n        }\n\n        {\n            INFO(\"pod5_get_file_read_table_location\")\n\n            EmbeddedFileData_t file_data{};\n            call_with_nulls(pod5_get_file_read_table_location, reader, &file_data);\n        }\n\n        {\n            INFO(\"pod5_get_file_signal_table_location\")\n\n            EmbeddedFileData_t file_data{};\n            call_with_nulls(pod5_get_file_signal_table_location, reader, &file_data);\n        }\n\n        {\n            INFO(\"pod5_get_file_run_info_table_location\")\n\n            EmbeddedFileData_t file_data{};\n            call_with_nulls(pod5_get_file_run_info_table_location, reader, &file_data);\n        }\n\n        {\n            INFO(\"pod5_get_read_count\")\n\n            size_t count{};\n            call_with_nulls(pod5_get_read_count, reader, &count);\n        }\n\n        {\n            INFO(\"pod5_get_read_ids\")\n\n            std::array<read_id_t, 3> read_ids{};\n            call_with_nulls(pod5_get_read_ids, reader, read_ids.size(), read_ids.data());\n        }\n\n        {\n            INFO(\"pod5_plan_traversal\")\n\n            constexpr std::size_t read_id_count = 1;\n            uint8_t const read_id_array[read_id_count * 16]{};\n            uint32_t batch_counts{};\n            uint32_t batch_rows{};\n            size_t find_success_count_out{};\n            call_with_nulls(\n                pod5_plan_traversal,\n                reader,\n                read_id_array,\n                read_id_count,\n                &batch_counts,\n                &batch_rows,\n                &find_success_count_out);\n        }\n\n        {\n            INFO(\"pod5_get_read_batch_count\")\n\n            size_t count{};\n            call_with_nulls(pod5_get_read_batch_count, &count, reader);\n        }\n\n        {\n            INFO(\"pod5_get_read_batch\")\n\n            Pod5ReadRecordBatch_t * batch = nullptr;\n            size_t index{};\n            call_with_nulls(pod5_get_read_batch, &batch, reader, index);\n        }\n\n        {\n            INFO(\"pod5_free_read_batch\")\n\n            pod5_reset_error();\n            CHECK_POD5_OK(pod5_free_read_batch(nullptr));\n        }\n\n        // The rest of these functions require a batch.\n        Pod5ReadRecordBatch_t * mutable_batch = nullptr;\n        CHECK_POD5_OK(pod5_get_read_batch(&mutable_batch, reader, 0));\n        REQUIRE(mutable_batch);\n        auto free_batch = gsl::finally([&mutable_batch] { pod5_free_read_batch(mutable_batch); });\n        Pod5ReadRecordBatch_t const * batch = mutable_batch;\n\n        {\n            INFO(\"pod5_get_read_batch_row_count\")\n\n            size_t count{};\n            call_with_nulls(pod5_get_read_batch_row_count, &count, batch);\n        }\n\n        {\n            INFO(\"pod5_get_read_batch_row_info_data\")\n\n            ReadBatchRowInfoV4 row_info{};\n            size_t row = 0;\n            uint16_t struct_version = READ_BATCH_ROW_INFO_VERSION;\n            uint16_t read_table_version{};\n            call_with_nulls(\n                pod5_get_read_batch_row_info_data,\n                batch,\n                row,\n                struct_version,\n                static_cast<void *>(&row_info),\n                &read_table_version);\n        }\n\n        {\n            INFO(\"pod5_get_signal_row_indices\")\n\n            size_t row = 0;\n            uint64_t indices[1];\n            call_with_nulls(\n                pod5_get_signal_row_indices,\n                batch,\n                row,\n                static_cast<int64_t>(std::size(indices)),\n                indices);\n        }\n\n        {\n            INFO(\"pod5_get_calibration_extra_info\")\n\n            size_t row = 0;\n            CalibrationExtraData_t data{};\n            call_with_nulls(pod5_get_calibration_extra_info, batch, row, &data);\n        }\n\n        {\n            INFO(\"pod5_get_run_info\")\n\n            int16_t index = 0;\n            RunInfoDictData_t * data = nullptr;\n            call_with_nulls(pod5_get_run_info, batch, index, &data);\n        }\n\n        {\n            INFO(\"pod5_get_file_run_info\")\n\n            run_info_index_t run_info_index = 0;\n            RunInfoDictData_t * run_info_data = nullptr;\n            call_with_nulls(pod5_get_file_run_info, reader, run_info_index, &run_info_data);\n        }\n\n        {\n            INFO(\"pod5_free_run_info\")\n\n            pod5_reset_error();\n            CHECK_POD5_OK(pod5_free_run_info(nullptr));\n        }\n\n        {\n            INFO(\"pod5_release_run_info\")\n\n            pod5_reset_error();\n#ifndef _WIN32\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wdeprecated-declarations\"\n#endif\n            CHECK_POD5_OK(pod5_release_run_info(nullptr));\n#ifndef _WIN32\n#pragma GCC diagnostic pop\n#endif\n        }\n\n        {\n            INFO(\"pod5_get_file_run_info_count\")\n\n            run_info_index_t count{};\n            call_with_nulls(pod5_get_file_run_info_count, reader, &count);\n        }\n\n        {\n            INFO(\"pod5_get_end_reason\")\n\n            int16_t index = 0;\n            pod5_end_reason end_reason{};\n            std::array<char, 10> string{};\n            size_t string_len = string.size();\n            call_with_nulls(\n                pod5_get_end_reason, batch, index, &end_reason, string.data(), &string_len);\n        }\n\n        {\n            INFO(\"pod5_get_pore_type\")\n\n            int16_t index = 0;\n            std::array<char, 10> string{};\n            size_t string_len = string.size();\n            call_with_nulls(pod5_get_pore_type, batch, index, string.data(), &string_len);\n        }\n\n        {\n            INFO(\"pod5_get_signal_row_info\")\n\n            std::array<uint64_t, 1> const signal_rows{};\n            SignalRowInfo * signal_row_info = nullptr;\n            call_with_nulls(\n                pod5_get_signal_row_info,\n                reader,\n                signal_rows.size(),\n                signal_rows.data(),\n                &signal_row_info);\n        }\n\n        {\n            INFO(\"pod5_free_signal_row_info\")\n\n            pod5_reset_error();\n            CHECK_POD5_OK(pod5_free_signal_row_info(0, nullptr));\n            CHECK_POD5_NOT_OK(pod5_free_signal_row_info(1, nullptr));\n        }\n\n        {\n            INFO(\"pod5_get_signal\")\n\n            // We need a signal row info.\n            uint64_t const signal_row_index = 0;\n            SignalRowInfo_t * signal_row_info = nullptr;\n            CHECK_POD5_OK(pod5_get_signal_row_info(reader, 1, &signal_row_index, &signal_row_info));\n            REQUIRE(signal_row_info);\n            auto free_signal_row_info = gsl::finally(\n                [&signal_row_info] { pod5_free_signal_row_info(1, &signal_row_info); });\n\n            std::array<int16_t, 10> samples{};\n            call_with_nulls(\n                pod5_get_signal,\n                reader,\n                static_cast<SignalRowInfo_t const *>(signal_row_info),\n                samples.size(),\n                samples.data());\n        }\n\n        {\n            INFO(\"pod5_get_read_complete_sample_count\")\n\n            size_t row = 0;\n            size_t count{};\n            call_with_nulls(pod5_get_read_complete_sample_count, reader, batch, row, &count);\n        }\n\n        {\n            INFO(\"pod5_get_read_complete_signal\")\n\n            size_t row = 1;\n            std::array<int16_t, 10> samples{};\n            call_with_nulls(\n                pod5_get_read_complete_signal, reader, batch, row, samples.size(), samples.data());\n        }\n    }\n\n    SECTION(\"Writer API\")\n    {\n        {\n            INFO(\"pod5_create_file\")\n\n            pod5_reset_error();\n            CHECK(pod5_create_file(nullptr, nullptr, nullptr) == nullptr);\n            CHECK_THAT(pod5_get_error_string(), Contains(\"null string passed\"));\n\n            pod5_reset_error();\n            CHECK(pod5_create_file(temporary_filename, nullptr, nullptr) == nullptr);\n            CHECK_THAT(pod5_get_error_string(), Contains(\"null string passed\"));\n\n            pod5_reset_error();\n            CHECK(pod5_create_file(nullptr, temporary_filename, nullptr) == nullptr);\n            CHECK_THAT(pod5_get_error_string(), Contains(\"null string passed\"));\n        }\n\n        {\n            INFO(\"pod5_close_and_free_writer\")\n\n            pod5_reset_error();\n            CHECK_POD5_OK(pod5_close_and_free_writer(nullptr));\n        }\n\n        // The rest of these functions require a writer.\n        REQUIRE(remove_file_if_exists(temporary_filename).ok());\n        Pod5FileWriter_t * writer = pod5_create_file(temporary_filename, \"c_software\", nullptr);\n        REQUIRE(writer);\n        auto close_writer = gsl::finally([&writer] { pod5_close_and_free_writer(writer); });\n\n        {\n            INFO(\"pod5_add_pore\")\n\n            int16_t pore_index{};\n            char const pore_type[] = \"test\";\n            call_with_nulls(pod5_add_pore, &pore_index, writer, pore_type);\n        }\n\n        {\n            INFO(\"pod5_add_run_info\")\n\n            int16_t run_info_index{};\n\n            char const dummy_string[] = \"test\";\n\n            char const * acquisition_id = dummy_string;\n            int64_t acquisition_start_time_ms = 1;\n            int16_t adc_max = 1;\n            int16_t adc_min = -1;\n            char const * experiment_name = dummy_string;\n            char const * flow_cell_id = dummy_string;\n            char const * flow_cell_product_code = dummy_string;\n            char const * protocol_name = dummy_string;\n            char const * protocol_run_id = dummy_string;\n            int64_t protocol_start_time_ms = 1;\n            char const * sample_id = dummy_string;\n            uint16_t sample_rate = 1;\n            char const * sequencing_kit = dummy_string;\n            char const * sequencer_position = dummy_string;\n            char const * sequencer_position_type = dummy_string;\n            char const * software = dummy_string;\n            char const * system_name = dummy_string;\n            char const * system_type = dummy_string;\n\n            size_t context_tags_count = 1;\n            char const * context_tags_keys[]{dummy_string};\n            char const * context_tags_values[]{dummy_string};\n\n            size_t tracking_id_count = 1;\n            char const * tracking_id_keys[]{dummy_string};\n            char const * tracking_id_values[]{dummy_string};\n\n            call_with_nulls(\n                pod5_add_run_info,\n                &run_info_index,\n                writer,\n                acquisition_id,\n                acquisition_start_time_ms,\n                adc_max,\n                adc_min,\n                context_tags_count,\n                context_tags_keys,\n                context_tags_values,\n                experiment_name,\n                flow_cell_id,\n                flow_cell_product_code,\n                protocol_name,\n                protocol_run_id,\n                protocol_start_time_ms,\n                sample_id,\n                sample_rate,\n                sequencing_kit,\n                sequencer_position,\n                sequencer_position_type,\n                software,\n                system_name,\n                system_type,\n                tracking_id_count,\n                tracking_id_keys,\n                tracking_id_values);\n        }\n\n        {\n            INFO(\"pod5_add_reads_data\")\n\n            uint32_t count = 1;\n            uint16_t version = READ_BATCH_ROW_INFO_VERSION;\n            ReadBatchRowInfoArray_t row_info{};\n            int16_t const signal[]{1, 2, 3, 4, 5};\n            int16_t const * signals[]{signal};\n            uint32_t const signal_size = std::size(signal);\n\n            call_with_nulls(\n                pod5_add_reads_data,\n                writer,\n                count,\n                version,\n                static_cast<void const *>(&row_info),\n                signals,\n                &signal_size);\n        }\n\n        {\n            INFO(\"pod5_add_reads_data_pre_compressed\")\n\n            uint32_t count = 1;\n            uint16_t version = READ_BATCH_ROW_INFO_VERSION;\n            ReadBatchRowInfoArray_t row_info{};\n\n            char const read0_compressed_signal_chunk0[]{1, 2, 3, 4, 5};\n            char const * read0_compressed_signal[]{read0_compressed_signal_chunk0};\n            size_t const read0_compressed_signal_sizes[]{std::size(read0_compressed_signal_chunk0)};\n            uint32_t const read0_sample_counts[]{3};\n            size_t const read0_signal_chunk_count = std::size(read0_compressed_signal);\n\n            char const ** compressed_signals[]{read0_compressed_signal};\n            size_t const * compressed_signal_sizes[]{read0_compressed_signal_sizes};\n            uint32_t const * sample_counts[]{read0_sample_counts};\n            size_t const signal_chunk_counts[]{read0_signal_chunk_count};\n\n            call_with_nulls(\n                pod5_add_reads_data_pre_compressed,\n                writer,\n                count,\n                version,\n                static_cast<void const *>(&row_info),\n                compressed_signals,\n                compressed_signal_sizes,\n                sample_counts,\n                signal_chunk_counts);\n        }\n    }\n\n    SECTION(\"VBZ API\")\n    {\n        {\n            INFO(\"pod5_vbz_compress_signal\")\n\n            std::array<int16_t, 10> const signal{};\n            std::array<char, 10> compressed{};\n            size_t compressed_size = compressed.size();\n            call_with_nulls(\n                pod5_vbz_compress_signal,\n                signal.data(),\n                signal.size(),\n                compressed.data(),\n                &compressed_size);\n        }\n\n        {\n            INFO(\"pod5_vbz_decompress_signal\")\n\n            std::array<char, 10> const compressed{};\n            std::array<int16_t, 10> signal{};\n            call_with_nulls(\n                pod5_vbz_decompress_signal,\n                compressed.data(),\n                compressed.size(),\n                signal.size(),\n                signal.data());\n        }\n    }\n\n    SECTION(\"Misc API\")\n    {\n        {\n            INFO(\"pod5_format_read_id\")\n\n            read_id_t const read_id{};\n            char * string = nullptr;\n            call_with_nulls(pod5_format_read_id, read_id, string);\n        }\n    }\n}\n\n}  // namespace\n"
  },
  {
    "path": "c++/test/c_api_test_utils.h",
    "content": "#pragma once\n\n#include \"pod5_format/c_api.h\"\n\n#include <catch2/catch.hpp>\n\n#include <string>\n\n#define CHECK_POD5_OK(statement)                                                    \\\n    do {                                                                            \\\n        auto const & _res = (statement);                                            \\\n        CHECK_THAT(testutils::Pod5C_Result::capture(_res), testutils::IsPod5COk()); \\\n    } while (false)\n\n#define REQUIRE_POD5_OK(statement)                                                    \\\n    do {                                                                              \\\n        auto const & _res = (statement);                                              \\\n        REQUIRE_THAT(testutils::Pod5C_Result::capture(_res), testutils::IsPod5COk()); \\\n    } while (false)\n\n#define CHECK_POD5_NOT_OK(statement)                                                 \\\n    do {                                                                             \\\n        auto const & _res = (statement);                                             \\\n        CHECK_THAT(testutils::Pod5C_Result::capture(_res), !testutils::IsPod5COk()); \\\n    } while (false)\n\n#define REQUIRE_POD5_NOT_OK(statement)                                                 \\\n    do {                                                                               \\\n        auto const & _res = (statement);                                               \\\n        REQUIRE_THAT(testutils::Pod5C_Result::capture(_res), !testutils::IsPod5COk()); \\\n    } while (false)\n\nnamespace testutils {\n\nstruct Pod5C_Result {\n    static Pod5C_Result capture(pod5_error_t err_num)\n    {\n        return Pod5C_Result{err_num, pod5_get_error_string()};\n    }\n\n    pod5_error_t error_code;\n    std::string error_string;\n};\n\nclass IsPod5COk : public Catch::MatcherBase<Pod5C_Result> {\npublic:\n    IsPod5COk() = default;\n\n    bool match(Pod5C_Result const & result) const override { return result.error_code == POD5_OK; }\n\n    virtual std::string describe() const override { return \"== POD5_OK\"; }\n};\n\n}  // namespace testutils\n\ntemplate <>\nstruct Catch::StringMaker<testutils::Pod5C_Result> {\n    static std::string convert(testutils::Pod5C_Result const & value)\n    {\n        return \"{ code: \" + std::to_string(value.error_code) + \"| \" + value.error_string + \" }\";\n    }\n};\n"
  },
  {
    "path": "c++/test/c_api_tests.cpp",
    "content": "#include \"pod5_format/c_api.h\"\n\n#include \"c_api_test_utils.h\"\n#include \"pod5_format/file_reader.h\"\n#include \"pod5_format/schema_metadata.h\"\n#include \"pod5_format/uuid.h\"\n#include \"pod5_format/version.h\"\n#include \"utils.h\"\n\n#include <catch2/catch.hpp>\n#include <gsl/gsl-lite.hpp>\n\n#include <algorithm>\n#include <iostream>\n#include <numeric>\n\nstruct Pod5ReadId {\n    Pod5ReadId() = default;\n\n    Pod5ReadId(pod5::Uuid const & uid) { uid.to_c_array(read_id); }\n\n    pod5::Uuid as_uuid() const { return pod5::Uuid{read_id}; }\n\n    bool operator==(Pod5ReadId const & other) const { return as_uuid() == other.as_uuid(); }\n\n    read_id_t read_id;\n};\n\nstd::ostream & operator<<(std::ostream & str, Pod5ReadId rid) { return str << rid.as_uuid(); }\n\nSCENARIO(\"C API Reads\")\n{\n    static constexpr char const * filename = \"./foo_c_api.pod5\";\n\n    pod5_init();\n    auto fin = gsl::finally([] { pod5_terminate(); });\n\n    std::mt19937 gen{Catch::rngSeed()};\n    auto uuid_gen = pod5::UuidRandomGenerator{gen};\n    auto input_read_id = uuid_gen();\n    auto input_read_id_2 = uuid_gen();\n    std::vector<int16_t> signal_1(10);\n    std::iota(signal_1.begin(), signal_1.end(), -20000);\n\n    std::vector<int16_t> signal_2(20);\n    std::iota(signal_2.begin(), signal_2.end(), 0);\n\n    std::int16_t adc_min = -4096;\n    std::int16_t adc_max = 4095;\n\n    float calibration_offset = 54.0f;\n    float calibration_scale = 100.0f;\n\n    float predicted_scale = 2.3f;\n    float predicted_shift = 10.0f;\n    float tracked_scale = 4.3f;\n    float tracked_shift = 15.0f;\n    std::uint32_t num_reads_since_mux_change = 1234;\n    float time_since_mux_change = 2.4f;\n    float open_pore_level = 123.0f;\n    std::uint64_t num_minknow_events = 104;\n\n    // Write the file:\n    {\n        CHECK_POD5_OK(pod5_get_error_no());\n        CHECK_FALSE(pod5_create_file(NULL, \"c_software\", NULL));\n        CHECK(pod5_get_error_no() == POD5_ERROR_INVALID);\n        CHECK_FALSE(pod5_create_file(\"\", \"c_software\", NULL));\n        CHECK(pod5_get_error_no() == POD5_ERROR_INVALID);\n        CHECK_FALSE(pod5_create_file(\"\", NULL, NULL));\n        CHECK(pod5_get_error_no() == POD5_ERROR_INVALID);\n\n        REQUIRE(remove_file_if_exists(filename).ok());\n\n        auto file = pod5_create_file(filename, \"c_software\", NULL);\n        REQUIRE(file);\n        CHECK_POD5_OK(pod5_get_error_no());\n\n        std::int16_t pore_type_id = -1;\n        CHECK_POD5_OK(pod5_add_pore(&pore_type_id, file, \"pore_type\"));\n        CHECK(pore_type_id == 0);\n\n        std::vector<char const *> context_tags_keys{\"thing\", \"foo\"};\n        std::vector<char const *> context_tags_values{\"thing_val\", \"foo_val\"};\n        std::vector<char const *> tracking_id_keys{\"baz\", \"other\"};\n        std::vector<char const *> tracking_id_values{\"baz_val\", \"other_val\"};\n\n        std::uint32_t read_number = 12;\n        std::uint64_t start_sample = 10245;\n        float median_before = 200.0f;\n        std::uint16_t channel = 43;\n        std::uint8_t well = 4;\n        pod5_end_reason_t end_reason = POD5_END_REASON_MUX_CHANGE;\n        uint8_t end_reason_forced = false;\n        auto read_id_array = (read_id_t const *)input_read_id.data();\n\n        std::int16_t run_info_id = 0;\n        ReadBatchRowInfoArrayV4 row_data{\n            read_id_array,\n            &read_number,\n            &start_sample,\n            &median_before,\n            &channel,\n            &well,\n            &pore_type_id,\n            &calibration_offset,\n            &calibration_scale,\n            &end_reason,\n            &end_reason_forced,\n            &run_info_id,\n            &num_minknow_events,\n            &tracked_scale,\n            &tracked_shift,\n            &predicted_scale,\n            &predicted_shift,\n            &num_reads_since_mux_change,\n            &time_since_mux_change,\n            &open_pore_level};\n\n        std::int16_t const * signal_arr[] = {signal_1.data()};\n        std::uint32_t signal_size[] = {(std::uint32_t)signal_1.size()};\n\n        // Referencing a non-existent run id should fail:\n        CHECK(\n            pod5_add_reads_data(\n                file, 1, READ_BATCH_ROW_INFO_VERSION_4, &row_data, signal_arr, signal_size)\n            == POD5_ERROR_INVALID);\n\n        // Now actually add the run info:\n        CHECK_POD5_OK(pod5_add_run_info(\n            &run_info_id,\n            file,\n            \"acquisition_id\",\n            15400,\n            adc_max,\n            adc_min,\n            context_tags_keys.size(),\n            context_tags_keys.data(),\n            context_tags_values.data(),\n            \"experiment_name\",\n            \"flow_cell_id\",\n            \"flow_cell_product_code\",\n            \"protocol_name\",\n            \"protocol_run_id\",\n            200000,\n            \"sample_id\",\n            4000,\n            \"sequencing_kit\",\n            \"sequencer_position\",\n            \"sequencer_position_type\",\n            \"software\",\n            \"system_name\",\n            \"system_type\",\n            tracking_id_keys.size(),\n            tracking_id_keys.data(),\n            tracking_id_values.data()));\n        CHECK(run_info_id == 0);\n\n        CHECK_POD5_OK(pod5_add_reads_data(\n            file, 1, READ_BATCH_ROW_INFO_VERSION_4, &row_data, signal_arr, signal_size));\n\n        {\n            auto compressed_read_max_size = pod5_vbz_compressed_signal_max_size(signal_2.size());\n            std::vector<char> compressed_signal(compressed_read_max_size);\n            char const * compressed_data[] = {compressed_signal.data()};\n            char const ** compressed_data_ptr = compressed_data;\n            std::size_t compressed_size[] = {compressed_signal.size()};\n            std::size_t const * compressed_size_ptr = compressed_size;\n            std::uint32_t signal_size[] = {(std::uint32_t)signal_2.size()};\n            std::uint32_t const * signal_size_ptr = signal_size;\n            pod5_vbz_compress_signal(\n                signal_2.data(), signal_2.size(), compressed_signal.data(), compressed_size);\n\n            std::size_t signal_counts = 1;\n\n            auto read_id_array = (read_id_t const *)input_read_id_2.data();\n            ReadBatchRowInfoArrayV3 row_data_v3{\n                read_id_array,\n                &read_number,\n                &start_sample,\n                &median_before,\n                &channel,\n                &well,\n                &pore_type_id,\n                &calibration_offset,\n                &calibration_scale,\n                &end_reason,\n                &end_reason_forced,\n                &run_info_id,\n                &num_minknow_events,\n                &tracked_scale,\n                &tracked_shift,\n                &predicted_scale,\n                &predicted_shift,\n                &num_reads_since_mux_change,\n                &time_since_mux_change};\n\n            CHECK_POD5_OK(pod5_add_reads_data_pre_compressed(\n                file,\n                1,\n                READ_BATCH_ROW_INFO_VERSION_3,\n                &row_data_v3,\n                &compressed_data_ptr,\n                &compressed_size_ptr,\n                &signal_size_ptr,\n                &signal_counts));\n        }\n\n        CHECK_POD5_OK(pod5_close_and_free_writer(file));\n        CHECK_POD5_OK(pod5_get_error_no());\n    }\n\n    // Read the file back:\n    {\n        CHECK_POD5_OK(pod5_get_error_no());\n        CHECK_FALSE(pod5_open_file(NULL));\n        auto file = pod5_open_file(filename);\n        CHECK_POD5_OK(pod5_get_error_no());\n        CHECK(file);\n\n        FileInfo_t file_info;\n        CHECK_POD5_OK(pod5_get_file_info(file, &file_info));\n        CHECK(file_info.version.major == pod5::Pod5MajorVersion);\n        CHECK(file_info.version.minor == pod5::Pod5MinorVersion);\n        CHECK(file_info.version.revision == pod5::Pod5RevVersion);\n        {\n            auto reader = pod5::open_file_reader(filename);\n            pod5::Uuid file_identifier{file_info.file_identifier};\n            CHECK(file_identifier == (*reader)->schema_metadata().file_identifier);\n        }\n\n        std::size_t read_count = 0;\n        CHECK_POD5_OK(pod5_get_read_count(file, &read_count));\n        REQUIRE(read_count == 2);\n\n        std::vector<Pod5ReadId> read_ids(2);\n        CHECK(pod5_get_read_ids(file, 1, (read_id_t *)read_ids.data()) != POD5_OK);\n        CHECK_POD5_OK(pod5_get_read_ids(file, read_ids.size(), (read_id_t *)read_ids.data()));\n        std::vector<Pod5ReadId> expected_read_ids{input_read_id, input_read_id_2};\n        CHECK(read_ids == expected_read_ids);\n\n        std::size_t batch_count = 0;\n        CHECK_POD5_OK(pod5_get_read_batch_count(&batch_count, file));\n        REQUIRE(batch_count == 1);\n\n        Pod5ReadRecordBatch * batch_0 = nullptr;\n        CHECK_POD5_OK(pod5_get_read_batch(&batch_0, file, 0));\n        REQUIRE(batch_0);\n\n        std::size_t row_count = 0;\n        CHECK_POD5_OK(pod5_get_read_batch_row_count(&row_count, batch_0));\n        REQUIRE(row_count == 2);\n\n        // Check out of bounds accesses get errors\n        {\n            ReadBatchRowInfoV4 v3_struct;\n            uint16_t input_version = 0;\n            CHECK(\n                pod5_get_read_batch_row_info_data(\n                    batch_0, row_count, READ_BATCH_ROW_INFO_VERSION, &v3_struct, &input_version)\n                == POD5_ERROR_INDEXERROR);\n\n            std::vector<uint64_t> signal_row_indices{1};\n            CHECK(\n                pod5_get_signal_row_indices(\n                    batch_0, row_count, signal_row_indices.size(), signal_row_indices.data())\n                == POD5_ERROR_INDEXERROR);\n\n            CalibrationExtraData calibration_extra_data{};\n            CHECK(\n                pod5_get_calibration_extra_info(batch_0, row_count, &calibration_extra_data)\n                == POD5_ERROR_INDEXERROR);\n        }\n\n        for (std::size_t row = 0; row < row_count; ++row) {\n            CAPTURE(row);\n            auto signal = signal_1;\n            if (row == 1) {\n                signal = signal_2;\n            }\n\n            static_assert(\n                std::is_same<ReadBatchRowInfoV4, ReadBatchRowInfo_t>::value,\n                \"Update this if new structs added\");\n\n            ReadBatchRowInfoV3 v3_struct;\n            ReadBatchRowInfoV4 v4_struct;\n            uint16_t input_version = 0;\n            CHECK_POD5_OK(pod5_get_read_batch_row_info_data(\n                batch_0, row, READ_BATCH_ROW_INFO_VERSION_3, &v3_struct, &input_version));\n            CHECK(\n                input_version\n                == 4);  // We're reading from a v4 file, even if the input struct is v3.\n            CHECK_POD5_OK(pod5_get_read_batch_row_info_data(\n                batch_0, row, READ_BATCH_ROW_INFO_VERSION_4, &v4_struct, &input_version));\n            CHECK(input_version == 4);\n\n            auto check_v3_or_v4 = [&](auto name, auto const & input_struct) {\n                CAPTURE(name);\n                std::string formatted_uuid(36, '\\0');\n                CHECK_POD5_OK(pod5_format_read_id(input_struct.read_id, &formatted_uuid[0]));\n                CHECK(\n                    formatted_uuid\n                    == to_string(*reinterpret_cast<pod5::Uuid const *>(input_struct.read_id)));\n\n                CHECK(input_struct.read_number == 12);\n                CHECK(input_struct.start_sample == 10245);\n                CHECK(input_struct.median_before == 200.0f);\n                CHECK(input_struct.channel == 43);\n                CHECK(input_struct.well == 4);\n                CHECK(input_struct.pore_type == 0);\n                CHECK(input_struct.calibration_offset == calibration_offset);\n                CHECK(input_struct.calibration_scale == calibration_scale);\n                CHECK(input_struct.end_reason == 1);\n                CHECK(input_struct.end_reason_forced == uint8_t{false});\n                CHECK(input_struct.run_info == 0);\n                CHECK(input_struct.num_minknow_events == num_minknow_events);\n                CHECK(input_struct.tracked_scaling_scale == tracked_scale);\n                CHECK(input_struct.tracked_scaling_shift == tracked_shift);\n                CHECK(input_struct.predicted_scaling_scale == predicted_scale);\n                CHECK(input_struct.predicted_scaling_shift == predicted_shift);\n                CHECK(input_struct.num_reads_since_mux_change == num_reads_since_mux_change);\n                CHECK(input_struct.time_since_mux_change == time_since_mux_change);\n                CHECK(input_struct.signal_row_count == 1);\n                CHECK(input_struct.num_samples == signal.size());\n            };\n\n            check_v3_or_v4(\"v3\", v3_struct);\n            check_v3_or_v4(\"v4\", v4_struct);\n            if (row == 0) {\n                CHECK(v4_struct.open_pore_level == open_pore_level);\n            } else {\n                CHECK(std::isnan(v4_struct.open_pore_level));\n            }\n\n            std::vector<uint64_t> signal_row_indices(v3_struct.signal_row_count);\n            CHECK_POD5_OK(pod5_get_signal_row_indices(\n                batch_0, row, signal_row_indices.size(), signal_row_indices.data()));\n\n            std::vector<SignalRowInfo *> signal_row_info(v3_struct.signal_row_count);\n            CHECK_POD5_OK(pod5_get_signal_row_info(\n                file,\n                signal_row_indices.size(),\n                signal_row_indices.data(),\n                signal_row_info.data()));\n\n            std::vector<int16_t> read_signal(signal_row_info.front()->stored_sample_count);\n            REQUIRE(signal_row_info.front()->stored_sample_count == signal.size());\n            CHECK_POD5_OK(pod5_get_signal(\n                file,\n                signal_row_info.front(),\n                signal_row_info.front()->stored_sample_count,\n                read_signal.data()));\n            CHECK(read_signal == signal);\n\n            std::size_t sample_count = 0;\n            CHECK_POD5_OK(pod5_get_read_complete_sample_count(file, batch_0, row, &sample_count));\n            CHECK(sample_count == signal_row_info.front()->stored_sample_count);\n            CHECK_POD5_OK(pod5_get_read_complete_signal(\n                file, batch_0, row, sample_count, read_signal.data()));\n            CHECK(read_signal == signal);\n\n            CHECK_POD5_OK(\n                pod5_free_signal_row_info(signal_row_indices.size(), signal_row_info.data()));\n\n            std::string expected_pore_type{\"pore_type\"};\n            std::array<char, 128> char_buffer{};\n            std::size_t returned_size = 2;  // deliberately too short!\n            {\n                CHECK(\n                    pod5_get_pore_type(\n                        batch_0, v3_struct.pore_type, char_buffer.data(), &returned_size)\n                    == POD5_ERROR_STRING_NOT_LONG_ENOUGH);\n                CHECK(returned_size == expected_pore_type.size() + 1);\n            }\n            {\n                returned_size = char_buffer.size();\n                CHECK_POD5_OK(pod5_get_pore_type(\n                    batch_0, v3_struct.pore_type, char_buffer.data(), &returned_size));\n                CHECK(returned_size == expected_pore_type.size() + 1);\n                CHECK(std::string{char_buffer.data()} == expected_pore_type);\n            }\n            {\n                returned_size = char_buffer.size();\n                CHECK(\n                    pod5_get_pore_type(batch_0, -1, char_buffer.data(), &returned_size)\n                    == POD5_ERROR_INDEXERROR);\n                CHECK(returned_size == char_buffer.size());\n            }\n\n            std::string expected_end_reason{\"mux_change\"};\n            {\n                returned_size = 2;  // deliberately too short!\n                pod5_end_reason end_reason = POD5_END_REASON_UNKNOWN;\n                CHECK(\n                    pod5_get_end_reason(\n                        batch_0,\n                        v3_struct.end_reason,\n                        &end_reason,\n                        char_buffer.data(),\n                        &returned_size)\n                    == POD5_ERROR_STRING_NOT_LONG_ENOUGH);\n                CHECK(returned_size == expected_end_reason.size() + 1);\n            }\n            {\n                returned_size = char_buffer.size();\n                pod5_end_reason end_reason = POD5_END_REASON_UNKNOWN;\n                CHECK_POD5_OK(pod5_get_end_reason(\n                    batch_0,\n                    v3_struct.end_reason,\n                    &end_reason,\n                    char_buffer.data(),\n                    &returned_size));\n                CHECK(returned_size == expected_end_reason.size() + 1);\n                CHECK(end_reason == POD5_END_REASON_MUX_CHANGE);\n                CHECK(std::string{char_buffer.data()} == expected_end_reason);\n            }\n            // Check getting with an invalid input end reason index:\n            {\n                returned_size = char_buffer.size();\n                pod5_end_reason end_reason = POD5_END_REASON_UNKNOWN;\n                CHECK(\n                    pod5_get_end_reason(\n                        batch_0,\n                        v3_struct.end_reason + 100,\n                        &end_reason,\n                        char_buffer.data(),\n                        &returned_size)\n                    == POD5_ERROR_INDEXERROR);\n                CHECK(returned_size == char_buffer.size());\n                CHECK(end_reason == POD5_END_REASON_UNKNOWN);\n            }\n\n            CalibrationExtraData calibration_extra_data{};\n            CHECK_POD5_OK(pod5_get_calibration_extra_info(batch_0, row, &calibration_extra_data));\n            CHECK(calibration_extra_data.digitisation == adc_max - adc_min + 1);\n            CHECK(calibration_extra_data.range == 8192 * calibration_scale);\n        }\n\n        SECTION(\"Embedded files\")\n        {\n            for (auto [get_file_location, name] : {\n                     std::make_tuple(\n                         pod5_get_file_read_table_location, \"pod5_get_file_read_table_location\"),\n                     std::make_tuple(\n                         pod5_get_file_signal_table_location,\n                         \"pod5_get_file_signal_table_location\"),\n                     std::make_tuple(\n                         pod5_get_file_run_info_table_location,\n                         \"pod5_get_file_run_info_table_location\"),\n                 })\n            {\n                CAPTURE(name);\n                EmbeddedFileData_t embedded_file_data{};\n                CHECK_POD5_OK(get_file_location(file, &embedded_file_data));\n                REQUIRE(embedded_file_data.file_name != nullptr);\n                CHECK(embedded_file_data.file_name == std::string_view{filename});\n                CHECK(embedded_file_data.offset > 0);\n                CHECK(embedded_file_data.length > 0);\n            }\n        }\n\n        run_info_index_t run_info_count = 0;\n        CHECK_POD5_OK(pod5_get_file_run_info_count(file, &run_info_count));\n        REQUIRE(run_info_count == 1);\n\n        // Check getting invalid run info indexes fails correctly.\n        RunInfoDictData * run_info_error = nullptr;\n        CHECK(pod5_get_run_info(batch_0, -1, &run_info_error) == POD5_ERROR_INDEXERROR);\n        CHECK_FALSE(run_info_error);\n        CHECK(pod5_get_run_info(batch_0, run_info_count, &run_info_error) == POD5_ERROR_INDEXERROR);\n        CHECK_FALSE(run_info_error);\n        CHECK(pod5_get_file_run_info(file, -1, &run_info_error) == POD5_ERROR_INDEXERROR);\n        CHECK_FALSE(run_info_error);\n        CHECK(\n            pod5_get_file_run_info(file, run_info_count, &run_info_error) == POD5_ERROR_INDEXERROR);\n        CHECK_FALSE(run_info_error);\n\n        auto check_run_info = [](RunInfoDictData * run_info) {\n            REQUIRE(run_info);\n            CHECK(run_info->tracking_id.size == 2);\n            CHECK(run_info->tracking_id.keys[0] == std::string(\"baz\"));\n            CHECK(run_info->tracking_id.keys[1] == std::string(\"other\"));\n            CHECK(run_info->tracking_id.values[0] == std::string(\"baz_val\"));\n            CHECK(run_info->tracking_id.values[1] == std::string(\"other_val\"));\n            CHECK(run_info->context_tags.size == 2);\n            CHECK(run_info->context_tags.keys[0] == std::string(\"thing\"));\n            CHECK(run_info->context_tags.keys[1] == std::string(\"foo\"));\n            CHECK(run_info->context_tags.values[0] == std::string(\"thing_val\"));\n            CHECK(run_info->context_tags.values[1] == std::string(\"foo_val\"));\n        };\n\n        RunInfoDictData * run_info_data_out_1 = nullptr;\n        CHECK_POD5_OK(pod5_get_file_run_info(file, 0, &run_info_data_out_1));\n        check_run_info(run_info_data_out_1);\n        pod5_free_run_info(run_info_data_out_1);\n\n        RunInfoDictData * run_info_data_out_2 = nullptr;\n        CHECK_POD5_OK(pod5_get_run_info(batch_0, 0, &run_info_data_out_2));\n        check_run_info(run_info_data_out_2);\n        pod5_free_run_info(run_info_data_out_2);\n\n        pod5_free_read_batch(batch_0);\n\n        pod5_close_and_free_reader(file);\n        CHECK_POD5_OK(pod5_get_error_no());\n    }\n}\n\nSCENARIO(\"C API Many Reads\")\n{\n    static constexpr char const * filename = \"./foo_c_api.pod5\";\n\n    pod5_init();\n    auto fin = gsl::finally([] { pod5_terminate(); });\n\n    std::mt19937 gen{Catch::rngSeed()};\n    auto uuid_gen = pod5::UuidRandomGenerator{gen};\n    std::vector<int16_t> signal_1(10);\n    std::iota(signal_1.begin(), signal_1.end(), -20000);\n\n    std::vector<int16_t> signal_2(20);\n    std::iota(signal_2.begin(), signal_2.end(), 0);\n\n    std::size_t const read_count = 10037;\n\n    std::int16_t const adc_min = -4096;\n    std::int16_t const adc_max = 4095;\n\n    std::vector<pod5::Uuid> read_id_array(read_count);\n    std::generate(read_id_array.begin(), read_id_array.end(), uuid_gen);\n\n    // Write the file:\n    {\n        CHECK_POD5_OK(pod5_get_error_no());\n        CHECK_FALSE(pod5_create_file(NULL, \"c_software\", NULL));\n        CHECK(pod5_get_error_no() == POD5_ERROR_INVALID);\n        CHECK_FALSE(pod5_create_file(\"\", \"c_software\", NULL));\n        CHECK(pod5_get_error_no() == POD5_ERROR_INVALID);\n        CHECK_FALSE(pod5_create_file(\"\", NULL, NULL));\n        CHECK(pod5_get_error_no() == POD5_ERROR_INVALID);\n\n        REQUIRE(remove_file_if_exists(filename).ok());\n\n        auto file = pod5_create_file(filename, \"c_software\", NULL);\n        REQUIRE(file);\n        CHECK_POD5_OK(pod5_get_error_no());\n\n        std::int16_t pore_type_id = -1;\n        CHECK_POD5_OK(pod5_add_pore(&pore_type_id, file, \"pore_type\"));\n        CHECK(pore_type_id == 0);\n\n        std::vector<char const *> context_tags_keys{\"thing\", \"foo\"};\n        std::vector<char const *> context_tags_values{\"thing_val\", \"foo_val\"};\n        std::vector<char const *> tracking_id_keys{\"baz\", \"other\"};\n        std::vector<char const *> tracking_id_values{\"baz_val\", \"other_val\"};\n\n        std::int16_t run_info_id = -1;\n        CHECK_POD5_OK(pod5_add_run_info(\n            &run_info_id,\n            file,\n            \"acquisition_id\",\n            15400,\n            adc_max,\n            adc_min,\n            context_tags_keys.size(),\n            context_tags_keys.data(),\n            context_tags_values.data(),\n            \"experiment_name\",\n            \"flow_cell_id\",\n            \"flow_cell_product_code\",\n            \"protocol_name\",\n            \"protocol_run_id\",\n            200000,\n            \"sample_id\",\n            4000,\n            \"sequencing_kit\",\n            \"sequencer_position\",\n            \"sequencer_position_type\",\n            \"software\",\n            \"system_name\",\n            \"system_type\",\n            tracking_id_keys.size(),\n            tracking_id_keys.data(),\n            tracking_id_values.data()));\n        CHECK(run_info_id == 0);\n\n        std::vector<std::uint32_t> read_number(read_count, 12);\n        std::vector<std::uint64_t> start_sample(read_count, 10245);\n        std::vector<float> median_before(read_count, 200.0f);\n        std::vector<std::uint16_t> channel(read_count, 43);\n        std::vector<std::uint8_t> well(read_count, 4);\n        std::vector<pod5_end_reason_t> end_reason(read_count, POD5_END_REASON_MUX_CHANGE);\n        std::vector<uint8_t> end_reason_forced(read_count, false);\n\n        std::vector<float> calibration_offset(read_count, 54.0f);\n        std::vector<float> calibration_scale(read_count, 100.0f);\n\n        std::vector<float> predicted_scale(read_count, 2.3f);\n        std::vector<float> predicted_shift(read_count, 10.0f);\n        std::vector<float> tracked_scale(read_count, 4.3f);\n        std::vector<float> tracked_shift(read_count, 15.0f);\n        std::vector<std::uint32_t> num_reads_since_mux_change(read_count, 1234);\n        std::vector<float> time_since_mux_change(read_count, 2.4f);\n        std::vector<float> open_pore_level(read_count, 123.0f);\n        std::vector<std::uint64_t> num_minknow_events(read_count, 104);\n\n        std::vector<std::int16_t> pore_type_ids(read_count, pore_type_id);\n        std::vector<std::int16_t> run_info_ids(read_count, run_info_id);\n\n        std::vector<std::int16_t const *> signal_arr;\n        std::vector<std::uint32_t> signal_size;\n        ReadBatchRowInfoArrayV4 row_data{\n            (read_id_t *)read_id_array.data(),\n            read_number.data(),\n            start_sample.data(),\n            median_before.data(),\n            channel.data(),\n            well.data(),\n            pore_type_ids.data(),\n            calibration_offset.data(),\n            calibration_scale.data(),\n            end_reason.data(),\n            end_reason_forced.data(),\n            run_info_ids.data(),\n            num_minknow_events.data(),\n            tracked_scale.data(),\n            tracked_shift.data(),\n            predicted_scale.data(),\n            predicted_shift.data(),\n            num_reads_since_mux_change.data(),\n            time_since_mux_change.data(),\n            open_pore_level.data()};\n\n        for (std::size_t i = 0; i < read_count; ++i) {\n            signal_arr.push_back(signal_1.data());\n            signal_size.push_back((std::uint32_t)signal_1.size());\n        }\n\n        CHECK_POD5_OK(pod5_add_reads_data(\n            file,\n            read_count,\n            READ_BATCH_ROW_INFO_VERSION_3,\n            &row_data,\n            signal_arr.data(),\n            signal_size.data()));\n\n        CHECK_POD5_OK(pod5_close_and_free_writer(file));\n        CHECK_POD5_OK(pod5_get_error_no());\n    }\n\n    // Read the file back:\n    {\n        Pod5ReaderOptions_t options{};\n        options.force_disable_file_mapping = true;\n\n        CHECK_POD5_OK(pod5_get_error_no());\n        CHECK_FALSE(pod5_open_file_options(NULL, &options));\n        CHECK_FALSE(pod5_open_file_options(filename, NULL));\n        auto file = pod5_open_file_options(filename, &options);\n        CHECK_POD5_OK(pod5_get_error_no());\n        CHECK(file);\n\n        FileInfo_t file_info;\n        CHECK_POD5_OK(pod5_get_file_info(file, &file_info));\n        CHECK(file_info.version.major == pod5::Pod5MajorVersion);\n        CHECK(file_info.version.minor == pod5::Pod5MinorVersion);\n        CHECK(file_info.version.revision == pod5::Pod5RevVersion);\n        {\n            auto reader = pod5::open_file_reader(filename);\n            pod5::Uuid file_identifier{file_info.file_identifier};\n            CHECK(file_identifier == (*reader)->schema_metadata().file_identifier);\n        }\n\n        std::size_t read_count_returned = 0;\n        CHECK_POD5_OK(pod5_get_read_count(file, &read_count_returned));\n        REQUIRE(read_count_returned == read_count);\n\n        // Randomise the order of the read IDs and then try and plan a path through them.\n        std::shuffle(read_id_array.begin(), read_id_array.end(), gen);\n        std::vector<std::uint32_t> batch_counts(read_count);\n        std::vector<std::uint32_t> batch_rows(read_count);\n        std::size_t find_success_count = 0;\n        CHECK_POD5_OK(pod5_plan_traversal(\n            file,\n            reinterpret_cast<uint8_t const *>(read_id_array.data()),\n            read_count,\n            batch_counts.data(),\n            batch_rows.data(),\n            &find_success_count));\n        REQUIRE(find_success_count == read_count);\n\n        CHECK_POD5_OK(pod5_close_and_free_reader(file));\n    }\n}\n\nSCENARIO(\"C API Run Info\")\n{\n    static constexpr char const * filename = \"./foo_c_api.pod5\";\n\n    pod5_init();\n    auto fin = gsl::finally([] { pod5_terminate(); });\n\n    std::int16_t adc_min = -4096;\n    std::int16_t adc_max = 4095;\n\n    auto expected_acq_id = [](std::size_t index) {\n        std::string acquisition_id{\"acquisition_id_\"};\n        acquisition_id += std::to_string(index);\n        return acquisition_id;\n    };\n\n    // Write the file:\n    {\n        REQUIRE(remove_file_if_exists(filename).ok());\n\n        auto file = pod5_create_file(filename, \"c_software\", NULL);\n        REQUIRE(file);\n        CHECK_POD5_OK(pod5_get_error_no());\n\n        std::vector<char const *> context_tags_keys{\"thing\", \"foo\"};\n        std::vector<char const *> context_tags_values{\"thing_val\", \"foo_val\"};\n        std::vector<char const *> tracking_id_keys{\"baz\", \"other\"};\n        std::vector<char const *> tracking_id_values{\"baz_val\", \"other_val\"};\n\n        for (std::size_t i = 0; i < 10; ++i) {\n            std::int16_t run_info_id = -1;\n            CHECK_POD5_OK(pod5_add_run_info(\n                &run_info_id,\n                file,\n                expected_acq_id(i).c_str(),\n                15400,\n                adc_max,\n                adc_min,\n                context_tags_keys.size(),\n                context_tags_keys.data(),\n                context_tags_values.data(),\n                \"experiment_name\",\n                \"flow_cell_id\",\n                \"flow_cell_product_code\",\n                \"protocol_name\",\n                \"protocol_run_id\",\n                200000,\n                \"sample_id\",\n                4000,\n                \"sequencing_kit\",\n                \"sequencer_position\",\n                \"sequencer_position_type\",\n                \"software\",\n                \"system_name\",\n                \"system_type\",\n                tracking_id_keys.size(),\n                tracking_id_keys.data(),\n                tracking_id_values.data()));\n            CHECK(run_info_id == static_cast<std::int16_t>(i));\n        }\n        CHECK_POD5_OK(pod5_close_and_free_writer(file));\n    }\n\n    // Read the file back:\n    {\n        CHECK_POD5_OK(pod5_get_error_no());\n        CHECK_FALSE(pod5_open_file(NULL));\n        auto file = pod5_open_file(filename);\n        CHECK_POD5_OK(pod5_get_error_no());\n        CHECK(pod5_get_error_string() == std::string{\"\"});\n        REQUIRE(file);\n\n        run_info_index_t run_info_count = 0;\n        CHECK_POD5_OK(pod5_get_file_run_info_count(file, &run_info_count));\n        REQUIRE(run_info_count == 10);\n\n        for (run_info_index_t i = 0; i < 10; ++i) {\n            RunInfoDictData * run_info_data_out = nullptr;\n            CHECK_POD5_OK(pod5_get_file_run_info(file, i, &run_info_data_out));\n            CHECK(run_info_data_out->acquisition_id == expected_acq_id(i));\n            pod5_free_run_info(run_info_data_out);\n        }\n\n        CHECK_POD5_OK(pod5_close_and_free_reader(file));\n    }\n}\n\nTEST_CASE(\"Missing file passed to pod5_open_file\")\n{\n    pod5_init();\n    auto cleanup = gsl::finally([] { pod5_terminate(); });\n\n    static constexpr char const temporary_filename[] = \"./foo_c_api.pod5\";\n    REQUIRE(remove_file_if_exists(temporary_filename).ok());\n\n    CHECK(pod5_open_file(temporary_filename) == nullptr);\n}\n\nTEST_CASE(\"Existing file passed to pod5_create_file\")\n{\n    pod5_init();\n    auto cleanup = gsl::finally([] { pod5_terminate(); });\n\n    static constexpr char const temporary_filename[] = \"./foo_c_api.pod5\";\n    REQUIRE(remove_file_if_exists(temporary_filename).ok());\n\n    // Create it once.\n    Pod5FileWriter_t * writer = pod5_create_file(temporary_filename, \"c_software\", nullptr);\n    REQUIRE_POD5_OK(pod5_get_error_no());\n    REQUIRE(writer != nullptr);\n    REQUIRE_POD5_OK(pod5_close_and_free_writer(writer));\n\n    // File already exists so this should fail.\n    CHECK(pod5_create_file(temporary_filename, \"c_software\", nullptr) == nullptr);\n}\n\nTEST_CASE(\"pod5_create_file with options\")\n{\n    pod5_init();\n    auto cleanup = gsl::finally([] { pod5_terminate(); });\n\n    static constexpr char const temporary_filename[] = \"./foo_c_api.pod5\";\n    REQUIRE(remove_file_if_exists(temporary_filename).ok());\n\n    Pod5WriterOptions_t test_options{};\n    Pod5WriterOptions_t const * options = nullptr;\n    bool const with_options = GENERATE(false, true);\n    if (with_options) {\n        options = &test_options;\n    } else {\n        test_options.max_signal_chunk_size = GENERATE(0, 1, 2);\n        test_options.signal_compression_type = GENERATE(\n            CompressionOption::DEFAULT_SIGNAL_COMPRESSION,\n            CompressionOption::VBZ_SIGNAL_COMPRESSION,\n            CompressionOption::UNCOMPRESSED_SIGNAL);\n        test_options.signal_table_batch_size = GENERATE(0, 1, 2);\n        test_options.read_table_batch_size = GENERATE(0, 1, 2);\n    }\n\n    CAPTURE(\n        with_options,\n        test_options.max_signal_chunk_size,\n        test_options.signal_compression_type,\n        test_options.signal_table_batch_size,\n        test_options.read_table_batch_size);\n\n    Pod5FileWriter_t * writer = pod5_create_file(temporary_filename, \"c_software\", options);\n    REQUIRE_POD5_OK(pod5_get_error_no());\n    REQUIRE(writer != nullptr);\n    REQUIRE_POD5_OK(pod5_close_and_free_writer(writer));\n}\n\nTEST_CASE(\"VBZ compression\")\n{\n    pod5_init();\n    auto cleanup = gsl::finally([] { pod5_terminate(); });\n\n    std::size_t const sample_count = 20;\n    std::vector<int16_t> input_signal(sample_count);\n    std::iota(input_signal.begin(), input_signal.end(), -sample_count / 2);\n\n    // Determine max size.\n    std::size_t const compressed_read_max_size =\n        pod5_vbz_compressed_signal_max_size(input_signal.size());\n    REQUIRE(compressed_read_max_size > 0);\n\n    // Compress it.\n    std::vector<char> compressed_signal(compressed_read_max_size);\n    std::size_t compressed_size = compressed_read_max_size;\n    REQUIRE_POD5_OK(pod5_vbz_compress_signal(\n        input_signal.data(), input_signal.size(), compressed_signal.data(), &compressed_size));\n    REQUIRE(compressed_size <= compressed_read_max_size);\n    compressed_signal.resize(compressed_size);\n\n    // Decompress it.\n    std::vector<int16_t> output_signal(sample_count);\n    REQUIRE_POD5_OK(pod5_vbz_decompress_signal(\n        compressed_signal.data(), compressed_signal.size(), sample_count, output_signal.data()));\n    REQUIRE(input_signal == output_signal);\n\n    // Providing incorrect buffer sizes should fail rather than crash.\n    CHECK_POD5_NOT_OK(pod5_vbz_decompress_signal(\n        compressed_signal.data(),\n        compressed_signal.size(),\n        sample_count * 2,\n        output_signal.data()));\n    std::size_t bad_compressed_size = compressed_size / 2;\n    CHECK_POD5_NOT_OK(pod5_vbz_compress_signal(\n        input_signal.data(), input_signal.size(), compressed_signal.data(), &bad_compressed_size));\n\n    // Going over the maximum size should produce an error.\n    size_t const max_size_error = pod5_vbz_compressed_signal_max_size(std::uint64_t{1} << 48);\n    CHECK(max_size_error == 0);\n    CHECK_POD5_NOT_OK(pod5_get_error_no());\n}\n"
  },
  {
    "path": "c++/test/file_reader_writer_tests.cpp",
    "content": "#include \"pod5_format/async_signal_loader.h\"\n#include \"pod5_format/file_reader.h\"\n#include \"pod5_format/file_writer.h\"\n#include \"pod5_format/internal/combined_file_utils.h\"\n#include \"pod5_format/read_table_reader.h\"\n#include \"pod5_format/signal_table_reader.h\"\n#include \"pod5_format/thread_pool.h\"\n#include \"pod5_format/uuid.h\"\n#include \"TemporaryDirectory.h\"\n#include \"test_utils.h\"\n#include \"utils.h\"\n\n#include <arrow/array/array_binary.h>\n#include <arrow/array/array_dict.h>\n#include <arrow/array/array_primitive.h>\n#include <arrow/io/file.h>\n#include <arrow/memory_pool.h>\n#include <catch2/catch.hpp>\n\n#include <chrono>\n#include <fstream>\n#include <numeric>\n#include <string>\n#include <thread>\n\nvoid run_file_reader_writer_tests(\n    char const * file,\n    pod5::FileWriterOptions const & extra_options = {})\n{\n    REQUIRE_ARROW_STATUS_OK(remove_file_if_exists(file));\n    (void)pod5::register_extension_types();\n    auto fin = gsl::finally([] { (void)pod5::unregister_extension_types(); });\n\n    auto const run_info_data = get_test_run_info_data(\"_run_info\");\n\n    std::mt19937 gen{Catch::rngSeed()};\n    auto uuid_gen = pod5::UuidRandomGenerator{gen};\n    auto read_id_1 = uuid_gen();\n\n    std::uint16_t channel = 25;\n    std::uint8_t well = 3;\n    std::uint32_t read_number = 1234;\n    std::uint64_t start_sample = 12340;\n    std::uint64_t num_minknow_events = 27;\n    float median_before = 224.0f;\n    float calib_offset = 22.5f;\n    float calib_scale = 1.2f;\n    float tracked_scaling_scale = 2.3f;\n    float tracked_scaling_shift = 100.0f;\n    float predicted_scaling_scale = 1.5f;\n    float predicted_scaling_shift = 50.0f;\n    std::uint32_t num_reads_since_mux_change = 3;\n    float time_since_mux_change = 200.0f;\n    float open_pore_level = 150.0f;\n\n    std::vector<std::int16_t> signal_1(100'000);\n    std::iota(signal_1.begin(), signal_1.end(), 0);\n\n    // Write a file:\n    {\n        pod5::FileWriterOptions options = extra_options;\n        options.set_max_signal_chunk_size(20'480);\n        options.set_read_table_batch_size(1);\n        options.set_signal_table_batch_size(5);\n\n        auto writer = pod5::create_file_writer(file, \"test_software\", options);\n        REQUIRE_ARROW_STATUS_OK(writer);\n\n        auto run_info = (*writer)->add_run_info(run_info_data);\n        auto end_reason = (*writer)->lookup_end_reason(pod5::ReadEndReason::signal_negative);\n        bool end_reason_forced = true;\n        auto pore_type = (*writer)->add_pore_type(\"Pore_type\");\n\n        for (std::size_t i = 0; i < 10; ++i) {\n            CHECK_ARROW_STATUS_OK((*writer)->add_complete_read(\n                {read_id_1,\n                 read_number,\n                 start_sample,\n                 channel,\n                 well,\n                 *pore_type,\n                 calib_offset,\n                 calib_scale,\n                 median_before,\n                 *end_reason,\n                 end_reason_forced,\n                 *run_info,\n                 num_minknow_events,\n                 tracked_scaling_scale,\n                 tracked_scaling_shift,\n                 predicted_scaling_scale,\n                 predicted_scaling_shift,\n                 num_reads_since_mux_change,\n                 time_since_mux_change,\n                 open_pore_level},\n                gsl::make_span(signal_1)));\n        }\n    }\n\n    // Open the file for reading:\n    // Write a file:\n    {\n        auto reader = pod5::open_file_reader(file, {});\n        REQUIRE_ARROW_STATUS_OK(reader);\n\n        REQUIRE((*reader)->num_read_record_batches() == 10);\n        for (std::size_t i = 0; i < 10; ++i) {\n            auto read_batch = (*reader)->read_read_record_batch(i);\n            REQUIRE_ARROW_STATUS_OK(read_batch);\n\n            auto read_id_array = read_batch->read_id_column();\n            CHECK(read_id_array->length() == 1);\n            CHECK(read_id_array->Value(0) == read_id_1);\n\n            auto columns = *read_batch->columns();\n            auto const run_info_dict_index =\n                std::dynamic_pointer_cast<arrow::Int16Array>(columns.run_info->indices())->Value(0);\n            CHECK(run_info_dict_index == 0);\n            auto const run_info_id = read_batch->get_run_info(run_info_dict_index);\n            CHECK(*run_info_id == run_info_data.acquisition_id);\n            auto const run_info = (*reader)->find_run_info(*run_info_id);\n            CHECK(**run_info == run_info_data);\n\n            REQUIRE((*reader)->num_signal_record_batches() == 10);\n            auto signal_batch = (*reader)->read_signal_record_batch(i);\n            REQUIRE_ARROW_STATUS_OK(signal_batch);\n\n            auto signal_read_id_array = signal_batch->read_id_column();\n            CHECK(signal_read_id_array->length() == 5);\n            CHECK(signal_read_id_array->Value(0) == read_id_1);\n            CHECK(signal_read_id_array->Value(1) == read_id_1);\n            CHECK(signal_read_id_array->Value(2) == read_id_1);\n            CHECK(signal_read_id_array->Value(3) == read_id_1);\n            CHECK(signal_read_id_array->Value(4) == read_id_1);\n\n            auto vbz_signal_array = signal_batch->vbz_signal_column();\n            CHECK(vbz_signal_array->length() == 5);\n\n            auto samples_array = signal_batch->samples_column();\n            CHECK(samples_array->Value(0) == 20'480);\n            CHECK(samples_array->Value(1) == 20'480);\n            CHECK(samples_array->Value(2) == 20'480);\n            CHECK(samples_array->Value(3) == 20'480);\n            CHECK(samples_array->Value(4) == 18'080);\n        }\n\n        auto const samples_mode = GENERATE(\n            pod5::AsyncSignalLoader::SamplesMode::NoSamples,\n            pod5::AsyncSignalLoader::SamplesMode::Samples);\n\n        pod5::AsyncSignalLoader async_no_samples_loader(\n            *reader,\n            samples_mode,\n            {},  // Read all the batches\n            {}   // No specific rows within batches)\n        );\n\n        for (std::size_t i = 0; i < 10; ++i) {\n            CAPTURE(i);\n            auto first_batch_res = async_no_samples_loader.release_next_batch();\n            REQUIRE_ARROW_STATUS_OK(first_batch_res);\n            auto first_batch = std::move(*first_batch_res);\n            CHECK(first_batch->batch_index() == i);\n\n            CHECK(first_batch->sample_count().size() == 1);\n            CHECK(first_batch->sample_count()[0] == signal_1.size());\n\n            CHECK(first_batch->samples().size() == 1);\n            if (samples_mode == pod5::AsyncSignalLoader::SamplesMode::Samples) {\n                CHECK(first_batch->samples()[0] == signal_1);\n            } else {\n                CHECK(first_batch->samples()[0].size() == 0);\n            }\n        }\n    }\n}\n\nSCENARIO(\"File Reader Writer Tests\") { run_file_reader_writer_tests(\"./foo.pod5\"); }\n\n#ifdef __linux__\nTEST_CASE(\"Additional make_file_stream() tests\")\n{\n    // When the user filesystem doesn't support direct-io, but it is requested then\n    // make_file_stream() should fallback to a \"regular\" FileOutputStream\n\n    // This because of the disk mounting, this test can only be run by someone or something that\n    // is effectively a root user.\n    if (::geteuid() != 0) {\n        WARN(\"SKIPPING TEST: Need root privileges to mount a test drive.\");\n        return;\n    }\n\n    std::filesystem::path const dir_path = \"./ramdisk_\" + std::to_string(std::time(nullptr));\n\n    // Create and mount tmpfs drive.\n    try {\n        std::filesystem::create_directory(dir_path);\n        auto const mount_cmd = std::string{\"mount -o size=500M -t tmpfs none \"} + dir_path.string();\n        auto const mount_return = std::system(mount_cmd.c_str());\n        if (mount_return != 0) {\n            // we have seen this fail in CI where the test thinks it can mount\n            // but CI fails to mount, just skip the test\n            WARN(\"SKIPPING TEST: Need root privileges to mount a test drive.\");\n            return;\n        }\n    } catch (std::exception const & e) {\n        FAIL(\"Failed to create and mount a tmpfs drive: \" << e.what());\n    }\n\n    auto const umount_cmd = std::string{\"umount \"} + dir_path.string();\n    auto remove_directory = gsl::finally([&] { std::filesystem::remove(dir_path); });\n    auto remove_mount = gsl::finally([&] { std::ignore = std::system(umount_cmd.c_str()); });\n\n    pod5::FileWriterOptions options_for_direct_io;\n    options_for_direct_io.set_use_directio(true);\n    options_for_direct_io.set_use_sync_io(true);\n    options_for_direct_io.set_write_chunk_size(524288);\n\n    try {\n        auto const test_file_path = dir_path / \"bar.pod5\";\n        run_file_reader_writer_tests(test_file_path.c_str(), options_for_direct_io);\n    } catch (std::exception const & e) {\n        FAIL(\"Failed to run file reader/writer tests: \" << e.what());\n    }\n}\n#endif\n\nSCENARIO(\"Opening older files\")\n{\n    (void)pod5::register_extension_types();\n    auto fin = gsl::finally([] { (void)pod5::unregister_extension_types(); });\n\n    auto uuid_from_string = [](char const * val) -> pod5::Uuid {\n        auto result = pod5::Uuid::from_string(val);\n        REQUIRE(result);\n        return *result;\n    };\n\n    struct ReadData {\n        pod5::Uuid read_id;\n        std::uint32_t read_number;\n        float calibration_offset;\n        float calibration_scale;\n        std::string end_reason;\n        std::string pore_type;\n        std::string run_info_id;\n    };\n\n    std::vector<ReadData> test_read_data{\n        {{uuid_from_string(\"0000173c-bf67-44e7-9a9c-1ad0bc728e74\")},\n         1093,\n         21.0f,\n         0.1755f,\n         \"unknown\",\n         \"not_set\",\n         \"a08e850aaa44c8b56765eee10b386fc3e516a62b\"},\n        {{uuid_from_string(\"002fde30-9e23-4125-9eae-d112c18a81a7\")},\n         75,\n         4.0f,\n         0.1755f,\n         \"unknown\",\n         \"not_set\",\n         \"a08e850aaa44c8b56765eee10b386fc3e516a62b\"},\n        {{uuid_from_string(\"006d1319-2877-4b34-85df-34de7250a47b\")},\n         1053,\n         6.0f,\n         0.1755f,\n         \"unknown\",\n         \"not_set\",\n         \"a08e850aaa44c8b56765eee10b386fc3e516a62b\"},\n        {{uuid_from_string(\"00728efb-2120-4224-87d8-580fbb0bd4b2\")},\n         657,\n         2.0f,\n         0.1755f,\n         \"unknown\",\n         \"not_set\",\n         \"a08e850aaa44c8b56765eee10b386fc3e516a62b\"},\n        {{uuid_from_string(\"007cc97e-6de2-4ff6-a0fd-1c1eca816425\")},\n         1625,\n         23.0f,\n         0.1755f,\n         \"unknown\",\n         \"not_set\",\n         \"a08e850aaa44c8b56765eee10b386fc3e516a62b\"},\n        {{uuid_from_string(\"008468c3-e477-46c4-a6e2-7d021a4ebf0b\")},\n         411,\n         4.0f,\n         0.1755f,\n         \"unknown\",\n         \"not_set\",\n         \"a08e850aaa44c8b56765eee10b386fc3e516a62b\"},\n        {{uuid_from_string(\"008ed3dc-86c2-452f-b107-6877a473d177\")},\n         513,\n         5.0f,\n         0.1755f,\n         \"unknown\",\n         \"not_set\",\n         \"a08e850aaa44c8b56765eee10b386fc3e516a62b\"},\n        {{uuid_from_string(\"00919556-e519-4960-8aa5-c2dfa020980c\")},\n         56,\n         2.0f,\n         0.1755f,\n         \"unknown\",\n         \"not_set\",\n         \"a08e850aaa44c8b56765eee10b386fc3e516a62b\"},\n        {{uuid_from_string(\"00925f34-6baf-47fc-b40c-22591e27fb5c\")},\n         930,\n         37.0f,\n         0.1755f,\n         \"unknown\",\n         \"not_set\",\n         \"a08e850aaa44c8b56765eee10b386fc3e516a62b\"},\n        {{uuid_from_string(\"009dc9bd-c5f4-487b-ba4c-b9ce7e3a711e\")},\n         195,\n         14.0f,\n         0.1755f,\n         \"unknown\",\n         \"not_set\",\n         \"a08e850aaa44c8b56765eee10b386fc3e516a62b\"},\n    };\n\n    auto repo_root =\n        ::arrow::internal::PlatformFilename::FromString(__FILE__)->Parent().Parent().Parent();\n    auto path = GENERATE_COPY(\n        *repo_root.Join(\"test_data/multi_fast5_zip_v0.pod5\"),\n        *repo_root.Join(\"test_data/multi_fast5_zip_v1.pod5\"),\n        *repo_root.Join(\"test_data/multi_fast5_zip_v2.pod5\"),\n        *repo_root.Join(\"test_data/multi_fast5_zip_v3.pod5\"),\n        *repo_root.Join(\"test_data/multi_fast5_zip_v4.pod5\"));\n    auto reader = pod5::open_file_reader(path.ToString(), {});\n    CHECK_ARROW_STATUS_OK(reader);\n\n    auto metadata = (*reader)->schema_metadata();\n    CHECK(metadata.writing_software == \"Python API\");\n\n    std::size_t abs_row = 0;\n\n    for (std::size_t i = 0; i < (*reader)->num_read_record_batches(); ++i) {\n        auto batch = (*reader)->read_read_record_batch(i);\n\n        auto columns = batch->columns();\n        REQUIRE_ARROW_STATUS_OK(columns);\n\n        for (std::size_t row = 0; row < batch->num_rows(); ++row) {\n            CAPTURE(abs_row);\n            auto read_data = test_read_data[row];\n            CHECK(columns->read_id->Value(row) == read_data.read_id);\n            CHECK(columns->read_number->Value(row) == read_data.read_number);\n            CHECK(columns->calibration_offset->Value(row) == read_data.calibration_offset);\n            CHECK(columns->calibration_scale->Value(row) == Approx(read_data.calibration_scale));\n            auto end_reason = *batch->get_end_reason(columns->end_reason->GetValueIndex(row));\n            CHECK(end_reason.first == pod5::end_reason_from_string(read_data.end_reason));\n            CHECK(end_reason.second == read_data.end_reason);\n            auto pore_type = batch->get_pore_type(columns->pore_type->GetValueIndex(row));\n            CHECK(*pore_type == read_data.pore_type);\n            auto run_info_id = batch->get_run_info(columns->run_info->GetValueIndex(row));\n            CHECK(*run_info_id == read_data.run_info_id);\n\n            ++abs_row;\n        }\n    }\n    CHECK(abs_row == test_read_data.size());\n\n    auto run_info = (*reader)->find_run_info(test_read_data[0].run_info_id);\n    REQUIRE_ARROW_STATUS_OK(run_info);\n    CHECK((*run_info)->acquisition_id == test_read_data[0].run_info_id);\n    CHECK((*run_info)->adc_min == -4096);\n    CHECK((*run_info)->adc_max == 4095);\n    CHECK((*run_info)->protocol_run_id == \"df049455-3552-438c-8176-d4a5b1dd9fc5\");\n    CHECK((*run_info)->software == \"python-pod5-converter\");\n    CHECK(\n        (*run_info)->tracking_id\n        == pod5::RunInfoData::MapType{\n            {\"asic_id\", \"131070\"},\n            {\"asic_id_eeprom\", \"0\"},\n            {\"asic_temp\", \"35.043102\"},\n            {\"asic_version\", \"IA02C\"},\n            {\"auto_update\", \"0\"},\n            {\"auto_update_source\", \"https://mirror.oxfordnanoportal.com/software/MinKNOW/\"},\n            {\"bream_is_standard\", \"0\"},\n            {\"device_id\", \"MS00000\"},\n            {\"device_type\", \"minion\"},\n            {\"distribution_status\", \"modified\"},\n            {\"distribution_version\", \"unknown\"},\n            {\"exp_script_name\", \"c449127e3461a521e0865fe6a88716f6f6b0b30c\"},\n            {\"exp_script_purpose\", \"sequencing_run\"},\n            {\"exp_start_time\", \"2019-05-13T11:11:43Z\"},\n            {\"flow_cell_id\", \"\"},\n            {\"guppy_version\", \"3.0.3+7e7b7d0\"},\n            {\"heatsink_temp\", \"35.000000\"},\n            {\"hostname\", \"happy_fish\"},\n            {\"installation_type\", \"prod\"},\n            {\"local_firmware_file\", \"1\"},\n            {\"operating_system\", \"ubuntu 16.04\"},\n            {\"protocol_group_id\", \"TEST_EXPERIMENT\"},\n            {\"protocol_run_id\", \"df049455-3552-438c-8176-d4a5b1dd9fc5\"},\n            {\"protocols_version\", \"4.0.6\"},\n            {\"run_id\", \"a08e850aaa44c8b56765eee10b386fc3e516a62b\"},\n            {\"sample_id\", \"TEST_SAMPLE\"},\n            {\"usb_config\", \"MinION_fx3_1.1.1_ONT#MinION_fpga_1.1.0#ctrl#Auto\"},\n            {\"version\", \"3.4.0-rc3\"},\n        });\n}\n\n/// Create empty file at \\p path.\nstatic void touch(std::filesystem::path const & path) { std::ofstream const ofs(path); }\n\n/// Create file containing bytes of value zero at \\p path.\nstatic void write_zeros(std::filesystem::path const & path)\n{\n    std::ofstream file_stream(path, std::ios::binary);\n    for (int i = 0; i < 1000000; ++i) {\n        file_stream.put('\\0');\n    }\n}\n\n/// Returns true iff the file exists and contains non-null data.\nstatic bool file_writing_started(std::filesystem::path const & file_path)\n{\n    if (!exists(file_path)) {\n        return false;\n    }\n    if (!is_regular_file(file_path)) {\n        return false;\n    }\n    // This should be enough for the check as unwritten files are usually\n    // empty or populated with nulls if writing has not been done.\n    auto const MINIMUM_BYTES_WRITTEN = 3;\n    if (file_size(file_path) < 3) {\n        return false;\n    }\n    std::ifstream file{file_path, std::ios::in | std::ios::binary};\n    for (auto byte_index = 0; byte_index < MINIMUM_BYTES_WRITTEN; ++byte_index) {\n        std::uint8_t byte;\n        file >> byte;\n        if (byte == 0) {\n            return false;\n        }\n    }\n    return true;\n}\n\nstatic bool files_ready_to_recover(std::filesystem::path const & directory_path)\n{\n    using directory_iterator = std::filesystem::directory_iterator;\n    // The directory should contain 3 files for recovery. A `pod5.tmp` with the signal data\n    // a `.tmp-reads` for the reads and a `.tmp-run-info` for the run information.\n    return std::count_if(\n               directory_iterator(directory_path), directory_iterator{}, file_writing_started)\n           >= 3;\n}\n\nstatic void wait_for_files_to_recover(std::filesystem::path const & directory_path)\n{\n    using clock = std::chrono::steady_clock;\n    auto const begin_waiting = clock::now();\n    auto const time_waited = [&]() {\n        return std::chrono::duration_cast<std::chrono::milliseconds>(clock::now() - begin_waiting);\n    };\n\n    while (!files_ready_to_recover(directory_path)) {\n        REQUIRE(time_waited() < std::chrono::milliseconds{100000});\n        // Give any asynchronous file writing threads a chance to write to disk, before we continue.\n        std::this_thread::sleep_for(std::chrono::milliseconds{100});\n    }\n}\n\nstatic std::filesystem::path create_files_for_recovery(\n    std::string const & file_name,\n    pod5::Uuid read_id_1,\n    ont::testutils::TemporaryDirectory & recovery_directory)\n{\n    auto const run_info_data = get_test_run_info_data(\"_run_info\");\n\n    std::uint16_t channel = 25;\n    std::uint8_t well = 3;\n    std::uint32_t read_number = 1234;\n    std::uint64_t start_sample = 12340;\n    std::uint64_t num_minknow_events = 27;\n    float median_before = 224.0f;\n    float calib_offset = 22.5f;\n    float calib_scale = 1.2f;\n    float tracked_scaling_scale = 2.3f;\n    float tracked_scaling_shift = 100.0f;\n    float predicted_scaling_scale = 1.5f;\n    float predicted_scaling_shift = 50.0f;\n    std::uint32_t num_reads_since_mux_change = 3;\n    float time_since_mux_change = 200.0f;\n    float open_pore_level = 150.0f;\n\n    std::vector<std::int16_t> signal_1(100'000);\n    std::iota(signal_1.begin(), signal_1.end(), 0);\n\n    ont::testutils::TemporaryDirectory data_writing_directory;\n    auto file = data_writing_directory.path() / file_name;\n\n    pod5::FileWriterOptions options;\n    options.set_max_signal_chunk_size(20'480);\n    options.set_read_table_batch_size(1);\n    options.set_signal_table_batch_size(5);\n    options.set_use_sync_io(true);\n    auto thread_pool = pod5::make_thread_pool(4);\n    options.set_thread_pool(thread_pool);\n\n    auto writer_result = pod5::create_file_writer(file.string(), \"test_software\", options);\n    REQUIRE_ARROW_STATUS_OK(writer_result);\n    std::unique_ptr<pod5::FileWriter> writer = std::move(*writer_result);\n\n    auto run_info = writer->add_run_info(run_info_data);\n    auto end_reason = writer->lookup_end_reason(pod5::ReadEndReason::signal_negative);\n    bool end_reason_forced = true;\n    auto pore_type = writer->add_pore_type(\"Pore_type\");\n\n    for (std::size_t i = 0; i < 10; ++i) {\n        CHECK_ARROW_STATUS_OK(writer->add_complete_read(\n            {read_id_1,\n             read_number,\n             start_sample,\n             channel,\n             well,\n             *pore_type,\n             calib_offset,\n             calib_scale,\n             median_before,\n             *end_reason,\n             end_reason_forced,\n             *run_info,\n             num_minknow_events,\n             tracked_scaling_scale,\n             tracked_scaling_shift,\n             predicted_scaling_scale,\n             predicted_scaling_shift,\n             num_reads_since_mux_change,\n             time_since_mux_change,\n             open_pore_level},\n            gsl::make_span(signal_1)));\n    }\n\n    wait_for_files_to_recover(data_writing_directory.path());\n\n    // Intermittent failures were seen on Windows, where the file was in the middle of being\n    // written when we copied it. This ensures that the file writing threads are done before\n    // we take the files.\n    thread_pool->wait_for_drain();\n\n    // The files are deliberately copied here before they can be properly finalised\n    // by the destructor of the FileWriter.\n    std::filesystem::copy(data_writing_directory.path(), recovery_directory.path());\n\n    REQUIRE(files_ready_to_recover(recovery_directory.path()));\n\n    return recovery_directory.path() / file_name;\n}\n\n/// This is equivalent to the C++20 `std::string::ends_with` function. It should be replaced with\n/// the standard library function once we move to the C++20 standard and drop support for building\n/// with GCC 8.\nstatic bool ends_with(std::string const & search_in, std::string const & suffix)\n{\n    if (suffix.size() > search_in.size()) {\n        return false;\n    }\n    return search_in.compare(search_in.size() - suffix.size(), std::string::npos, suffix) == 0;\n}\n\nTEST_CASE(\"Check custom rolled ends_with works\", \"[string_utilities]\")\n{\n    CHECK(ends_with(\"abc\", \"abc\"));\n    CHECK(ends_with(\"abcdef\", \"def\"));\n    CHECK_FALSE(ends_with(\"abcdef\", \"abc\"));\n    CHECK_FALSE(ends_with(\"def\", \"abcdef\"));\n    CHECK_FALSE(ends_with(\"abc\", \"def\"));\n}\n\nstatic std::string escape_for_regex(std::string const & input)\n{\n    std::string output;\n    for (auto const & character : input) {\n        switch (character) {\n        case '\\\\':\n        case '/':\n        case '.':\n        case '[':\n        case ']':\n        case '(':\n        case ')':\n            output += std::string(\"\\\\\");\n        default:;\n        }\n        output += character;\n    }\n    return output;\n}\n\nTEST_CASE(\"Recovering .pod5.tmp files\", \"[recovery]\")\n{\n    std::string const file_name = \"foo.pod5.tmp\";\n    ont::testutils::TemporaryDirectory recovery_directory;\n    auto const registration_status = pod5::register_extension_types();\n    REQUIRE(registration_status.ok());\n    auto const unregister = [] { (void)pod5::unregister_extension_types(); };\n    auto fin = std::make_unique<gsl::final_action<decltype(unregister)>>(unregister);\n    std::mt19937 gen{Catch::rngSeed()};\n    auto uuid_gen = pod5::UuidRandomGenerator{gen};\n    std::filesystem::path const path_to_recover =\n        create_files_for_recovery(file_name, uuid_gen(), recovery_directory);\n\n    REQUIRE(exists(path_to_recover));\n    std::filesystem::path reads_path, run_path;\n    for (auto const & directory_entry :\n         std::filesystem::directory_iterator{recovery_directory.path()})\n    {\n        if (!directory_entry.is_regular_file()) {\n            continue;\n        }\n        if (ends_with(directory_entry.path().filename().string(), (\".tmp-reads\"))) {\n            reads_path = directory_entry.path();\n        }\n        if (ends_with(directory_entry.path().filename().string(), (\".tmp-run-info\"))) {\n            run_path = directory_entry.path();\n        }\n    }\n    REQUIRE(exists(reads_path));\n    REQUIRE(exists(run_path));\n    auto const recovered_file_path = recovery_directory.path() / (file_name + \"-recovered.pod5\");\n    // Confirm that no recovered file is left over from previous test runs.\n    REQUIRE_FALSE(exists(recovered_file_path));\n\n    // Paths are implicitly convertible to the kind of strings used for paths\n    // on the current platform. On Windows this is an `std::wstring`, but the\n    // recover_file_writer takes a `std::string`, so we need the explicit\n    // conversion to make the build work on that platform.\n    // `generic_string()` is used rather than `native()` because Arrow paths\n    // always use `/` as a separator, even on Windows.\n    std::string const to_recover = path_to_recover.generic_string();\n    std::string const recovered = recovered_file_path.generic_string();\n\n    bool const cleanup = GENERATE(true, false);\n    pod5::RecoverFileOptions const options{.cleanup = cleanup};\n\n    CAPTURE(to_recover, recovered, cleanup);\n\n    SECTION(\"Recovering basic set of .tmp files.\")\n    {\n        auto const recovery_details = pod5::recover_file(to_recover, recovered, options);\n        REQUIRE_ARROW_STATUS_OK(recovery_details);\n        CHECK(exists(recovered_file_path));\n        CHECK(recovery_details->row_counts.run_info == 1);\n        CHECK(recovery_details->row_counts.signal == 50);\n        CHECK(recovery_details->row_counts.reads == 10);\n        CHECK(recovery_details->cleanup_errors.empty());\n        if (cleanup) {\n            CHECK_FALSE(exists(path_to_recover));\n            CHECK_FALSE(exists(reads_path));\n            CHECK_FALSE(exists(run_path));\n        } else {\n            CHECK(exists(path_to_recover));\n            CHECK(exists(reads_path));\n            CHECK(exists(run_path));\n        }\n    }\n\n    SECTION(\"Recovering whilst extensions are not registered.\")\n    {\n        fin = {};\n        auto recover_result2 = pod5::recover_file(to_recover, recovered, options);\n        REQUIRE_FALSE(recover_result2.ok());\n        REQUIRE(\n            recover_result2.status().ToString()\n            == \"Invalid: POD5 library is not correctly initialised.\");\n        CHECK(exists(path_to_recover));\n        CHECK(exists(reads_path));\n        CHECK(exists(run_path));\n    }\n\n    SECTION(\"Recovering without run information.\")\n    {\n        remove(run_path);\n        std::string const run_info_string = run_path.generic_string();\n        CAPTURE(run_info_string);\n\n        SECTION(\"Recovering set of .tmp files with run info file missing.\")\n        {\n            auto recover_result3 = pod5::recover_file(to_recover, recovered, options);\n            REQUIRE_FALSE(recover_result3.ok());\n            auto const result_message3 = recover_result3.status().ToString();\n            auto const expected_regex3 =\n                \"IOError: Failed whilst attempting to recover run information from file - \"\n                + escape_for_regex(run_info_string) + R\"(\\. Detail: \\[(errno|Windows error) 2\\] )\"\n                + R\"((No such file or directory|The system cannot find the file specified)[.\\n\\r]*)\";\n            REQUIRE_THAT(result_message3, Catch::Matchers::Matches(expected_regex3));\n            if (cleanup) {\n                CHECK(exists(path_to_recover));\n                CHECK(exists(reads_path));\n                CHECK_FALSE(exists(run_path));\n                CHECK_FALSE(exists(recovered_file_path));\n            } else {\n                CHECK(exists(path_to_recover));\n                CHECK(exists(reads_path));\n            }\n        }\n\n        SECTION(\"Recovering set of .tmp files with run info file empty.\")\n        {\n            touch(run_path);\n            auto recover_result4 = pod5::recover_file(to_recover, recovered, options);\n            REQUIRE_FALSE(recover_result4.ok());\n            REQUIRE(\n                recover_result4.status().ToString()\n                == \"Invalid: Failed whilst attempting to recover run information from file - \"\n                       + run_info_string + \". Detail: File is empty/zero bytes long.\");\n            if (cleanup) {\n                CHECK(exists(path_to_recover));\n                CHECK(exists(reads_path));\n                CHECK_FALSE(exists(run_path));\n                CHECK_FALSE(exists(recovered_file_path));\n            } else {\n                CHECK(exists(path_to_recover));\n                CHECK(exists(reads_path));\n                CHECK(exists(run_path));\n            }\n        }\n\n        SECTION(\"Recovering set of .tmp files with run info file zeroed.\")\n        {\n            write_zeros(run_path);\n            auto recover_result5 = pod5::recover_file(to_recover, recovered, options);\n            REQUIRE_FALSE(recover_result5.ok());\n            REQUIRE(\n                recover_result5.status().ToString()\n                == \"Invalid: Failed whilst attempting to recover run information from file - \"\n                       + run_info_string + \". Detail: Not an Arrow file\");\n            if (cleanup) {\n                CHECK(exists(path_to_recover));\n                CHECK(exists(reads_path));\n                CHECK_FALSE(exists(run_path));\n                CHECK_FALSE(exists(recovered_file_path));\n            } else {\n                CHECK(exists(path_to_recover));\n                CHECK(exists(reads_path));\n                CHECK(exists(run_path));\n            }\n        }\n    }\n\n    SECTION(\"Recovering without read information.\")\n    {\n        remove(reads_path);\n        std::string const reads_string = reads_path.generic_string();\n        CAPTURE(reads_string);\n\n        SECTION(\"Recovering set of .tmp files with reads file missing.\")\n        {\n            auto recover_result6 = pod5::recover_file(to_recover, recovered, options);\n            REQUIRE_FALSE(recover_result6.ok());\n            auto const result_message6 = recover_result6.status().ToString();\n            auto const expected_regex6 =\n                \"IOError: Failed whilst attempting to recover reads from file - \"\n                + escape_for_regex(reads_string) + R\"(\\. Detail: \\[(errno|Windows error) 2\\] )\"\n                + R\"((No such file or directory|The system cannot find the file specified)[.\\n\\r]*)\";\n            REQUIRE_THAT(result_message6, Catch::Matchers::Matches(expected_regex6));\n            if (cleanup) {\n                CHECK(exists(path_to_recover));\n                CHECK_FALSE(exists(reads_path));\n                CHECK(exists(run_path));\n                CHECK_FALSE(exists(recovered_file_path));\n            } else {\n                CHECK(exists(path_to_recover));\n                CHECK(exists(run_path));\n            }\n        }\n\n        SECTION(\"Recovering set of .tmp files with reads file empty.\")\n        {\n            touch(reads_path);\n            auto recover_result7 = pod5::recover_file(to_recover, recovered, options);\n            REQUIRE_FALSE(recover_result7.ok());\n            REQUIRE(\n                recover_result7.status().ToString()\n                == \"Invalid: Failed whilst attempting to recover reads from file - \" + reads_string\n                       + \". Detail: File is empty/zero bytes long.\");\n            if (cleanup) {\n                CHECK(exists(path_to_recover));\n                CHECK_FALSE(exists(reads_path));\n                CHECK(exists(run_path));\n                CHECK_FALSE(exists(recovered_file_path));\n            } else {\n                CHECK(exists(path_to_recover));\n                CHECK(exists(reads_path));\n                CHECK(exists(run_path));\n            }\n        }\n\n        SECTION(\"Recovering set of .tmp files with reads file zeroed.\")\n        {\n            write_zeros(reads_path);\n            auto recover_result7 = pod5::recover_file(to_recover, recovered, options);\n            REQUIRE_FALSE(recover_result7.ok());\n            REQUIRE(\n                recover_result7.status().ToString()\n                == \"Invalid: Failed whilst attempting to recover reads from file - \" + reads_string\n                       + \". Detail: Not an Arrow file\");\n            if (cleanup) {\n                CHECK(exists(path_to_recover));\n                CHECK_FALSE(exists(reads_path));\n                CHECK(exists(run_path));\n                CHECK_FALSE(exists(recovered_file_path));\n            } else {\n                CHECK(exists(path_to_recover));\n                CHECK(exists(reads_path));\n                CHECK(exists(run_path));\n            }\n        }\n    }\n\n    SECTION(\"Error messages for problems with combined .pod5.tmp file.\")\n    {\n        remove(path_to_recover);\n\n        SECTION(\"Recovering set of .tmp files with .pod5.tmp file missing.\")\n        {\n            auto recover_result8 = pod5::recover_file(to_recover, recovered, options);\n            REQUIRE_FALSE(recover_result8.ok());\n            auto const result_message = recover_result8.status().ToString();\n            auto const expected_regex =\n                \"IOError: Failed to open local file '\" + escape_for_regex(to_recover)\n                + R\"('\\. Detail: \\[(errno|Windows error) 2\\] )\"\n                + R\"((No such file or directory|The system cannot find the file specified)[.\\n\\r]*)\";\n            CAPTURE(result_message, expected_regex);\n            REQUIRE_THAT(result_message, Catch::Matchers::Matches(expected_regex));\n            if (cleanup) {\n                CHECK_FALSE(exists(recovered_file_path));\n            }\n            CHECK_FALSE(exists(path_to_recover));\n            CHECK(exists(reads_path));\n            CHECK(exists(run_path));\n        }\n\n        SECTION(\"Recovering set of .tmp files with .pod5.tmp file empty.\")\n        {\n            touch(path_to_recover);\n            auto recover_result9 = pod5::recover_file(to_recover, recovered, options);\n            REQUIRE_FALSE(recover_result9.ok());\n            REQUIRE(recover_result9.status().ToString() == \"IOError: Invalid signature in file\");\n            if (cleanup) {\n                CHECK_FALSE(exists(recovered_file_path));\n                CHECK_FALSE(exists(path_to_recover));\n            } else {\n                CHECK(exists(path_to_recover));\n            }\n            CHECK(exists(reads_path));\n            CHECK(exists(run_path));\n        }\n\n        SECTION(\"Recovering set of .tmp files with .pod5.tmp file zeroed.\")\n        {\n            write_zeros(path_to_recover);\n            auto recover_result10 = pod5::recover_file(to_recover, recovered, options);\n            REQUIRE_FALSE(recover_result10.ok());\n            REQUIRE(recover_result10.status().ToString() == \"IOError: Invalid signature in file\");\n            if (cleanup) {\n                CHECK_FALSE(exists(recovered_file_path));\n                CHECK_FALSE(exists(path_to_recover));\n            } else {\n                CHECK(exists(path_to_recover));\n            }\n            CHECK(exists(reads_path));\n            CHECK(exists(run_path));\n        }\n\n        arrow::Result<std::shared_ptr<arrow::io::FileOutputStream>> result_tmp_file =\n            arrow::io::FileOutputStream::Open(to_recover, false);\n        REQUIRE_ARROW_STATUS_OK(result_tmp_file);\n        std::shared_ptr<arrow::io::FileOutputStream> tmp_file = std::move(*result_tmp_file);\n        REQUIRE_ARROW_STATUS_OK(pod5::combined_file_utils::write_file_signature(tmp_file));\n\n        SECTION(\"Recover .pod5.tmp missing section marker after signature.\")\n        {\n            tmp_file = {};\n            auto recover_result11 = pod5::recover_file(to_recover, recovered, options);\n            REQUIRE_FALSE(recover_result11.ok());\n            REQUIRE(recover_result11.status().ToString() == \"IOError: Invalid offset into SubFile\");\n            if (cleanup) {\n                CHECK_FALSE(exists(recovered_file_path));\n            }\n            CHECK(exists(path_to_recover));\n            CHECK(exists(reads_path));\n            CHECK(exists(run_path));\n        }\n\n        SECTION(\"Recover .pod5.tmp missing signal sub file.\")\n        {\n            pod5::Uuid section_id = uuid_gen();\n            REQUIRE_ARROW_STATUS_OK(tmp_file->Write(section_id.data(), section_id.size()));\n            tmp_file = {};\n            auto recover_result12 = pod5::recover_file(to_recover, recovered, options);\n            REQUIRE_FALSE(recover_result12.ok());\n            REQUIRE(\n                recover_result12.status().ToString()\n                == \"Invalid: Failed whilst attempting to recover signal data sub file from file - \"\n                       + to_recover + \". Detail: Not an Arrow file\");\n            if (cleanup) {\n                CHECK_FALSE(exists(recovered_file_path));\n            }\n            CHECK(exists(path_to_recover));\n            CHECK(exists(reads_path));\n            CHECK(exists(run_path));\n        }\n    }\n}\n"
  },
  {
    "path": "c++/test/main.cpp",
    "content": "#define CATCH_CONFIG_MAIN\n#include <catch2/catch.hpp>\n"
  },
  {
    "path": "c++/test/output_stream_tests.cpp",
    "content": "#include \"pod5_format/internal/async_output_stream.h\"\n#include \"pod5_format/internal/linux_output_stream.h\"\n#include \"test_utils.h\"\n\n#include <arrow/io/file.h>\n#include <catch2/catch.hpp>\n\n#include <fstream>\n#include <sstream>\n\nnamespace {\nstatic constexpr std::size_t TestDataSize = 1024 * 1024 * 100;\n\nstd::shared_ptr<arrow::Buffer> get_test_data()\n{\n    static std::shared_ptr<arrow::Buffer> const data = [] {\n        auto result = *arrow::AllocateResizableBuffer(TestDataSize);\n        for (std::size_t i = 0; i < TestDataSize; ++i) {\n            result->mutable_data()[i] = i % 256;\n        }\n        return result;\n    }();\n\n    return data;\n}\n\nstd::vector<char> read_file(char const * filename)\n{\n    std::ifstream fin(filename, std::ios::binary);\n\n    return std::vector<char>(std::istreambuf_iterator<char>(fin), std::istreambuf_iterator<char>());\n}\n\nvoid check_file_contents(char const * filename)\n{\n    auto contents = read_file(filename);\n    auto expected_contents = get_test_data();\n    auto expected_contents_span = expected_contents->span_as<char>();\n\n    REQUIRE(contents.size() == expected_contents_span.size());\n\n    for (std::size_t i = 0; i < expected_contents_span.size(); i += 1) {\n        CHECK(contents[i] == expected_contents_span[i]);\n    }\n}\n}  // namespace\n\nvoid run_output_stream_test(std::shared_ptr<arrow::io::OutputStream> output_stream)\n{\n    auto const data = get_test_data();\n\n    std::size_t small_writes_bytes_consumed = 0;\n    {\n        CHECK_ARROW_STATUS_OK(output_stream->Write(data->data() + small_writes_bytes_consumed, 1));\n        small_writes_bytes_consumed += 1;\n        CHECK_ARROW_STATUS_OK(output_stream->Write(data->data() + small_writes_bytes_consumed, 2));\n        small_writes_bytes_consumed += 2;\n        CHECK_ARROW_STATUS_OK(output_stream->Write(data->data() + small_writes_bytes_consumed, 4));\n        small_writes_bytes_consumed += 4;\n        CHECK_ARROW_STATUS_OK(output_stream->Write(data->data() + small_writes_bytes_consumed, 8));\n        small_writes_bytes_consumed += 8;\n\n        CHECK_ARROW_STATUS_OK(output_stream->Flush());\n    }\n\n    auto remaining_data_buffer = arrow::SliceBuffer(data, small_writes_bytes_consumed);\n\n    {\n        auto chunk_1 = arrow::SliceBuffer(remaining_data_buffer, 0, 1024);\n        auto chunk_2 = arrow::SliceBuffer(remaining_data_buffer, 1024, 63);\n        remaining_data_buffer = arrow::SliceBuffer(remaining_data_buffer, 1024 + 63);\n        CHECK_ARROW_STATUS_OK(output_stream->Write(chunk_1));\n        CHECK_ARROW_STATUS_OK(output_stream->Write(chunk_2));\n        CHECK_ARROW_STATUS_OK(output_stream->Flush());\n    }\n\n    {\n        auto chunk_1 = arrow::SliceBuffer(remaining_data_buffer, 0, 1024 * 1024);\n        auto chunk_2 = arrow::SliceBuffer(remaining_data_buffer, 1024 * 1024, 1023);\n        remaining_data_buffer = arrow::SliceBuffer(remaining_data_buffer, 1024 * 1024 + 1023);\n        CHECK_ARROW_STATUS_OK(output_stream->Write(chunk_1));\n        CHECK_ARROW_STATUS_OK(output_stream->Write(chunk_2));\n        CHECK_ARROW_STATUS_OK(output_stream->Flush());\n    }\n\n    CHECK_ARROW_STATUS_OK(output_stream->Write(remaining_data_buffer));\n    CHECK_ARROW_STATUS_OK(output_stream->Flush());\n}\n\nTEST_CASE(\"AsyncOutputStream\", \"[OutputStream]\")\n{\n    using namespace pod5;\n\n    auto const filename = \"./test_file.bin\";\n    {\n        std::ofstream f(filename, std::ios_base::trunc);\n    }\n    {\n        bool keep_file_open = GENERATE(true, false);\n\n        auto thread_pool = make_thread_pool(1);\n        auto stream = *AsyncOutputStream::make(\n            filename, thread_pool, true, arrow::default_memory_pool(), keep_file_open);\n\n        run_output_stream_test(stream);\n    }\n    check_file_contents(filename);\n}\n\n#ifdef __linux__\nTEST_CASE(\"LinuxOutputStream IOManagerSyncImpl\", \"[OutputStream]\")\n{\n    using namespace pod5;\n\n    bool keep_file_open = GENERATE(true, false);\n    CAPTURE(keep_file_open);\n\n    auto filename = \"./test_file.bin\";\n    {\n        std::ofstream f(filename, std::ios_base::trunc);\n    }\n    {\n        auto io_manager = pod5::make_sync_io_manager();\n        REQUIRE_ARROW_STATUS_OK(io_manager);\n        auto stream = *LinuxOutputStream::make(\n            filename, *io_manager, 10 * 1024 * 1024, true, false, true, keep_file_open);\n\n        run_output_stream_test(stream);\n    }\n    check_file_contents(filename);\n}\n\n#endif\n"
  },
  {
    "path": "c++/test/read_table_tests.cpp",
    "content": "#include \"pod5_format/internal/async_output_stream.h\"\n#include \"pod5_format/read_table_reader.h\"\n#include \"pod5_format/read_table_writer.h\"\n#include \"pod5_format/schema_metadata.h\"\n#include \"pod5_format/types.h\"\n#include \"pod5_format/uuid.h\"\n#include \"pod5_format/version.h\"\n#include \"test_utils.h\"\n#include \"utils.h\"\n\n#include <arrow/array/array_dict.h>\n#include <arrow/array/array_nested.h>\n#include <arrow/array/array_primitive.h>\n#include <arrow/io/file.h>\n#include <arrow/memory_pool.h>\n#include <arrow/record_batch.h>\n#include <catch2/catch.hpp>\n\nbool operator==(\n    std::shared_ptr<arrow::UInt64Array> const & array,\n    std::vector<std::uint64_t> const & vec)\n{\n    auto const length = static_cast<std::size_t>(array->length());\n    if (length != vec.size()) {\n        return false;\n    }\n\n    for (std::size_t i = 0; i < length; ++i) {\n        if ((*array)[i] != vec[i]) {\n            return false;\n        }\n    }\n    return true;\n}\n\nSCENARIO(\"Read table Tests\")\n{\n    using namespace pod5;\n\n    (void)pod5::register_extension_types();\n    auto fin = gsl::finally([] { (void)pod5::unregister_extension_types(); });\n\n    std::mt19937 gen{Catch::rngSeed()};\n    auto uuid_gen = pod5::UuidRandomGenerator{gen};\n\n    auto file_identifier = uuid_gen();\n\n    auto data_for_index = [&](std::size_t index) {\n        std::array<std::uint8_t, 16> uuid_source{};\n        Uuid read_id{uuid_source};\n\n        return std::make_tuple(\n            pod5::ReadData{\n                read_id,\n                std::uint32_t(index * 2),\n                std::uint64_t(index * 10),\n                std::uint16_t(index + 1),\n                std::uint8_t(index + 2),\n                0,\n                index * 0.1f,\n                index * 0.2f,\n                index * 100.0f,\n                0,\n                true,\n                0,\n                std::uint64_t(index * 150),\n                index * 0.4f,\n                index * 0.3f,\n                index * 0.6f,\n                index * 0.5f,\n                std::uint32_t(index + 10),\n                index * 50.0f,\n                index * 0.7f},\n            std::vector<std::uint64_t>{index + 2, index + 3});\n    };\n\n    GIVEN(\"A read table writer\")\n    {\n        auto filename = \"./foo.pod5\";\n        auto pool = arrow::system_memory_pool();\n\n        auto const record_batch_count = GENERATE(as<std::size_t>{}, 1, 2, 5, 10);\n        auto const read_count = GENERATE(1, 2);\n\n        {\n            auto file_out =\n                *pod5::AsyncOutputStream::make(filename, pod5::make_thread_pool(1), true);\n            auto schema_metadata = make_schema_key_value_metadata(\n                {file_identifier, \"test_software\", *parse_version_number(Pod5Version)});\n            REQUIRE_ARROW_STATUS_OK(schema_metadata);\n\n            auto pore_writer = pod5::make_pore_writer(pool);\n            REQUIRE_ARROW_STATUS_OK(pore_writer);\n            auto end_reason_writer = pod5::make_end_reason_writer(pool);\n            REQUIRE_ARROW_STATUS_OK(end_reason_writer);\n            auto run_info_writer = pod5::make_run_info_writer(pool);\n            REQUIRE_ARROW_STATUS_OK(run_info_writer);\n\n            auto writer = pod5::make_read_table_writer(\n                file_out,\n                *schema_metadata,\n                read_count,\n                *pore_writer,\n                *end_reason_writer,\n                *run_info_writer,\n                pool);\n            REQUIRE_ARROW_STATUS_OK(writer);\n\n            auto const pore_1 = (*pore_writer)->add(\"Well Type\");\n            REQUIRE_ARROW_STATUS_OK(pore_1);\n            auto const end_reason_1 = (*end_reason_writer)->lookup(pod5::ReadEndReason::mux_change);\n            REQUIRE_ARROW_STATUS_OK(end_reason_1);\n            auto const run_info_1 = (*run_info_writer)->add(\"acq_id_1\");\n            REQUIRE_ARROW_STATUS_OK(run_info_1);\n            auto const run_info_2 = (*run_info_writer)->add(\"acq_id_2\");\n            REQUIRE_ARROW_STATUS_OK(run_info_2);\n\n            for (std::size_t i = 0; i < record_batch_count; ++i) {\n                for (std::size_t j = 0; j < static_cast<std::size_t>(read_count); ++j) {\n                    auto const idx = j + i * read_count;\n\n                    pod5::ReadData read_data;\n                    std::vector<std::uint64_t> signal;\n                    std::tie(read_data, signal) = data_for_index(idx);\n                    auto row = writer->add_read(read_data, signal, signal.size());\n\n                    REQUIRE_ARROW_STATUS_OK(row);\n                    CHECK(*row == idx);\n                }\n            }\n            REQUIRE_ARROW_STATUS_OK(writer->close());\n        }\n\n        auto file_in = arrow::io::ReadableFile::Open(filename, pool);\n        {\n            REQUIRE_ARROW_STATUS_OK(file_in);\n\n            auto reader = pod5::make_read_table_reader(*file_in, pool);\n            REQUIRE_ARROW_STATUS_OK(reader);\n\n            auto metadata = reader->schema_metadata();\n            CHECK(metadata.file_identifier == file_identifier);\n            CHECK(metadata.writing_software == \"test_software\");\n            CHECK(metadata.writing_pod5_version == *parse_version_number(Pod5Version));\n\n            REQUIRE(reader->num_record_batches() == record_batch_count);\n            for (std::size_t i = 0; i < record_batch_count; ++i) {\n                auto const record_batch = reader->read_record_batch(i);\n                REQUIRE_ARROW_STATUS_OK(record_batch);\n                REQUIRE(record_batch->num_rows() == static_cast<std::size_t>(read_count));\n\n                auto columns = record_batch->columns();\n\n                CHECK(columns->read_id->length() == read_count);\n                CHECK(columns->signal->length() == read_count);\n                CHECK(columns->channel->length() == read_count);\n                CHECK(columns->well->length() == read_count);\n                CHECK(columns->pore_type->length() == read_count);\n                CHECK(columns->calibration_offset->length() == read_count);\n                CHECK(columns->calibration_scale->length() == read_count);\n                CHECK(columns->read_number->length() == read_count);\n                CHECK(columns->start_sample->length() == read_count);\n                CHECK(columns->median_before->length() == read_count);\n                CHECK(columns->num_samples->length() == read_count);\n                CHECK(columns->end_reason->length() == read_count);\n                CHECK(columns->end_reason_forced->length() == read_count);\n                CHECK(columns->run_info->length() == read_count);\n\n                auto pore_indices =\n                    std::static_pointer_cast<arrow::Int16Array>(columns->pore_type->indices());\n                auto end_reason_indices =\n                    std::static_pointer_cast<arrow::Int16Array>(columns->end_reason->indices());\n                auto run_info_indices =\n                    std::static_pointer_cast<arrow::Int16Array>(columns->run_info->indices());\n                for (auto j = 0; j < read_count; ++j) {\n                    auto idx = j + i * read_count;\n\n                    pod5::ReadData read_data;\n                    std::vector<std::uint64_t> expected_signal;\n                    std::tie(read_data, expected_signal) = data_for_index(idx);\n\n                    CHECK(columns->read_id->Value(j) == read_data.read_id);\n\n                    auto signal_data = std::static_pointer_cast<arrow::UInt64Array>(\n                        columns->signal->value_slice(j));\n                    CHECK(\n                        gsl::make_span(signal_data->raw_values(), signal_data->length())\n                        == gsl::make_span(expected_signal));\n\n                    CHECK(columns->read_number->Value(j) == read_data.read_number);\n                    CHECK(columns->start_sample->Value(j) == read_data.start_sample);\n                    CHECK(columns->median_before->Value(j) == read_data.median_before);\n                    CHECK(columns->num_samples->Value(j) == expected_signal.size());\n                    CHECK(columns->calibration_offset->Value(j) == read_data.calibration_offset);\n                    CHECK(columns->calibration_scale->Value(j) == read_data.calibration_scale);\n                    CHECK(columns->channel->Value(j) == read_data.channel);\n                    CHECK(columns->well->Value(j) == read_data.well);\n\n                    CHECK(end_reason_indices->Value(j) == read_data.end_reason);\n                    CHECK(pore_indices->Value(j) == read_data.pore_type);\n                    CHECK(run_info_indices->Value(j) == read_data.run_info);\n                }\n\n                auto pore_data = record_batch->get_pore_type(0);\n                REQUIRE_ARROW_STATUS_OK(pore_data);\n                CHECK(*pore_data == \"Well Type\");\n\n                auto end_reason_data = record_batch->get_end_reason(1);\n                REQUIRE_ARROW_STATUS_OK(end_reason_data);\n                CHECK(end_reason_data->first == pod5::ReadEndReason::mux_change);\n                CHECK(end_reason_data->second == \"mux_change\");\n\n                auto run_info_data = record_batch->get_run_info(0);\n                REQUIRE_ARROW_STATUS_OK(run_info_data);\n                CHECK(*run_info_data == \"acq_id_1\");\n\n                run_info_data = record_batch->get_run_info(1);\n                REQUIRE_ARROW_STATUS_OK(run_info_data);\n                CHECK(*run_info_data == \"acq_id_2\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "c++/test/read_table_writer_utils_tests.cpp",
    "content": "#include \"pod5_format/read_table_writer_utils.h\"\n\n#include \"test_utils.h\"\n#include \"utils.h\"\n\n#include <arrow/array/array_binary.h>\n#include <arrow/array/array_nested.h>\n#include <arrow/array/array_primitive.h>\n#include <arrow/memory_pool.h>\n#include <catch2/catch.hpp>\n\nTEST_CASE(\"Run Info Writer Tests\")\n{\n    auto pool = arrow::system_memory_pool();\n    auto run_info_writer = pod5::make_run_info_writer(pool);\n    REQUIRE_ARROW_STATUS_OK(run_info_writer);\n\n    auto index = (*run_info_writer)->add(\"acq_id_1\");\n    CHECK(*index == 0);\n    CHECK((*run_info_writer)->item_count() == 1);\n\n    // Important to always call this so we test calling it twice\n    auto const value_array = (*run_info_writer)->get_value_array();\n\n    WHEN(\"Checking the first row\")\n    {\n        REQUIRE_ARROW_STATUS_OK(value_array);\n\n        auto string_value_array = std::dynamic_pointer_cast<arrow::StringArray>(*value_array);\n        REQUIRE(string_value_array);\n\n        CHECK(string_value_array->length() == 1);\n        CHECK(string_value_array->Value(0) == \"acq_id_1\");\n    }\n\n    index = (*run_info_writer)->add(\"acq_id_2\");\n    CHECK(*index == 1);\n    CHECK((*run_info_writer)->item_count() == 2);\n\n    WHEN(\"Checking the rows after a second append\")\n    {\n        auto value_array = (*run_info_writer)->get_value_array();\n        REQUIRE_ARROW_STATUS_OK(value_array);\n\n        auto string_value_array = std::dynamic_pointer_cast<arrow::StringArray>(*value_array);\n        REQUIRE(string_value_array);\n\n        CHECK(string_value_array->length() == 2);\n        CHECK(string_value_array->Value(0) == \"acq_id_1\");\n        CHECK(string_value_array->Value(1) == \"acq_id_2\");\n    }\n}\n"
  },
  {
    "path": "c++/test/run_info_table_tests.cpp",
    "content": "#include \"pod5_format/internal/async_output_stream.h\"\n#include \"pod5_format/run_info_table_reader.h\"\n#include \"pod5_format/run_info_table_writer.h\"\n#include \"pod5_format/schema_metadata.h\"\n#include \"pod5_format/types.h\"\n#include \"pod5_format/uuid.h\"\n#include \"pod5_format/version.h\"\n#include \"test_utils.h\"\n#include \"utils.h\"\n\n#include <arrow/array/array_dict.h>\n#include <arrow/array/array_nested.h>\n#include <arrow/array/array_primitive.h>\n#include <arrow/io/file.h>\n#include <arrow/memory_pool.h>\n#include <arrow/record_batch.h>\n#include <catch2/catch.hpp>\n\nSCENARIO(\"Run Info table Tests\")\n{\n    using namespace pod5;\n\n    (void)pod5::register_extension_types();\n    auto fin = gsl::finally([] { (void)pod5::unregister_extension_types(); });\n\n    std::mt19937 gen{Catch::rngSeed()};\n    auto uuid_gen = pod5::UuidRandomGenerator{gen};\n\n    auto file_identifier = uuid_gen();\n\n    GIVEN(\"A read table writer\")\n    {\n        auto filename = \"./foo.pod5\";\n        auto pool = arrow::system_memory_pool();\n\n        auto run_info_data_0 = get_test_run_info_data();\n        auto run_info_data_1 = get_test_run_info_data(\"_2\");\n\n        {\n            auto file_out =\n                *pod5::AsyncOutputStream::make(filename, pod5::make_thread_pool(1), true);\n            auto schema_metadata = make_schema_key_value_metadata(\n                {file_identifier, \"test_software\", *parse_version_number(Pod5Version)});\n            REQUIRE_ARROW_STATUS_OK(schema_metadata);\n\n            std::size_t run_info_per_batch = 2;\n\n            auto writer = pod5::make_run_info_table_writer(\n                file_out, *schema_metadata, run_info_per_batch, pool);\n            REQUIRE_ARROW_STATUS_OK(writer);\n\n            REQUIRE_ARROW_STATUS_OK(writer->add_run_info(run_info_data_0));\n            REQUIRE_ARROW_STATUS_OK(writer->add_run_info(run_info_data_1));\n        }\n\n        auto file_in = arrow::io::ReadableFile::Open(filename, pool);\n        {\n            REQUIRE_ARROW_STATUS_OK(file_in);\n\n            auto reader = pod5::make_run_info_table_reader(*file_in, pool);\n            REQUIRE_ARROW_STATUS_OK(reader);\n\n            auto metadata = reader->schema_metadata();\n            CHECK(metadata.file_identifier == file_identifier);\n            CHECK(metadata.writing_software == \"test_software\");\n            CHECK(metadata.writing_pod5_version == *parse_version_number(Pod5Version));\n\n            REQUIRE(reader->num_record_batches() == 1);\n            auto const record_batch = reader->read_record_batch(0);\n            REQUIRE_ARROW_STATUS_OK(record_batch);\n            REQUIRE(record_batch->num_rows() == 2);\n\n            auto columns = record_batch->columns();\n            REQUIRE_ARROW_STATUS_OK(columns);\n\n            auto check_run_info = [](auto & columns,\n                                     std::size_t index,\n                                     pod5::RunInfoData const & run_info_data) {\n                CHECK(columns.acquisition_id->Value(index) == run_info_data.acquisition_id);\n\n                CHECK(\n                    columns.acquisition_start_time->Value(index)\n                    == run_info_data.acquisition_start_time);\n                CHECK(columns.adc_max->Value(index) == run_info_data.adc_max);\n                CHECK(columns.adc_min->Value(index) == run_info_data.adc_min);\n                CHECK(columns.experiment_name->Value(index) == run_info_data.experiment_name);\n                CHECK(columns.flow_cell_id->Value(index) == run_info_data.flow_cell_id);\n                CHECK(\n                    columns.flow_cell_product_code->Value(index)\n                    == run_info_data.flow_cell_product_code);\n                CHECK(columns.protocol_name->Value(index) == run_info_data.protocol_name);\n                CHECK(columns.protocol_run_id->Value(index) == run_info_data.protocol_run_id);\n                CHECK(\n                    columns.protocol_start_time->Value(index) == run_info_data.protocol_start_time);\n                CHECK(columns.sample_id->Value(index) == run_info_data.sample_id);\n                CHECK(columns.sample_rate->Value(index) == run_info_data.sample_rate);\n                CHECK(columns.sequencing_kit->Value(index) == run_info_data.sequencing_kit);\n                CHECK(columns.sequencer_position->Value(index) == run_info_data.sequencer_position);\n                CHECK(\n                    columns.sequencer_position_type->Value(index)\n                    == run_info_data.sequencer_position_type);\n                CHECK(columns.software->Value(index) == run_info_data.software);\n                CHECK(columns.system_name->Value(index) == run_info_data.system_name);\n                CHECK(columns.system_type->Value(index) == run_info_data.system_type);\n            };\n\n            check_run_info(*columns, 0, run_info_data_0);\n            check_run_info(*columns, 1, run_info_data_1);\n\n            auto found_run_info_0 = reader->find_run_info(run_info_data_0.acquisition_id);\n            CHECK_ARROW_STATUS_OK(found_run_info_0);\n            CHECK(**found_run_info_0 == run_info_data_0);\n\n            auto found_run_info_1 = reader->find_run_info(run_info_data_1.acquisition_id);\n            CHECK_ARROW_STATUS_OK(found_run_info_1);\n            CHECK(**found_run_info_1 == run_info_data_1);\n        }\n    }\n}\n"
  },
  {
    "path": "c++/test/schema_tests.cpp",
    "content": "#include \"pod5_format/schema_metadata.h\"\n#include \"test_utils.h\"\n\n#include <catch2/catch.hpp>\n\nSCENARIO(\"Version Tests\")\n{\n    using namespace pod5;\n\n    CHECK(Version(1, 2, 3) < Version(3, 2, 1));\n    CHECK(Version(1, 2, 3) < Version(1, 3, 3));\n    CHECK(Version(1, 2, 3) < Version(1, 2, 4));\n\n    CHECK(Version(3, 2, 1) > Version(1, 2, 3));\n    CHECK(Version(1, 3, 3) > Version(1, 2, 3));\n    CHECK(Version(1, 2, 4) > Version(1, 2, 3));\n\n    CHECK(Version(1, 2, 3) == Version(1, 2, 3));\n\n    CHECK(Version(1, 2, 3) != Version(2, 2, 3));\n    CHECK(Version(1, 2, 3) != Version(1, 3, 3));\n    CHECK(Version(1, 2, 3) != Version(1, 2, 4));\n\n    CHECK_ARROW_STATUS_NOT_OK(parse_version_number(\"1.2.3.4\"));\n    CHECK_ARROW_STATUS_NOT_OK(parse_version_number(\"1.2.3-pre\"));\n\n    auto const parsed_version = parse_version_number(\"10.200.3\");\n    REQUIRE_ARROW_STATUS_OK(parsed_version);\n    CHECK(Version(10, 200, 3) == *parsed_version);\n    CHECK(parsed_version->major_version() == 10);\n    CHECK(parsed_version->minor_version() == 200);\n    CHECK(parsed_version->revision_version() == 3);\n\n    CHECK(Version(1, 200, 30).to_string() == \"1.200.30\");\n}\n"
  },
  {
    "path": "c++/test/signal_compression_tests.cpp",
    "content": "#include \"pod5_format/signal_compression.h\"\n\n#include \"test_utils.h\"\n#include \"utils.h\"\n\n#include <arrow/buffer.h>\n#include <arrow/memory_pool.h>\n#include <catch2/catch.hpp>\n#include <gsl/gsl-lite.hpp>\n\n#include <numeric>\n\nSCENARIO(\"Signal compression Tests\")\n{\n    auto pool = arrow::system_memory_pool();\n\n    std::vector<std::int16_t> signal(100'00);\n    std::iota(signal.begin(), signal.end(), 0);\n\n    auto compressed = pod5::compress_signal(gsl::make_span(signal), pool);\n    REQUIRE_ARROW_STATUS_OK(compressed);\n    auto compressed_span = gsl::make_span((*compressed)->data(), (*compressed)->size());\n\n    auto decompressed = pod5::decompress_signal(compressed_span, signal.size(), pool);\n    REQUIRE_ARROW_STATUS_OK(decompressed);\n    auto decompressed_span = gsl::make_span((*decompressed)->data(), (*decompressed)->size())\n                                 .as_span<std::int16_t const>();\n\n    CHECK(gsl::make_span(signal) == decompressed_span);\n}\n"
  },
  {
    "path": "c++/test/signal_table_tests.cpp",
    "content": "#include \"pod5_format/internal/async_output_stream.h\"\n#include \"pod5_format/schema_metadata.h\"\n#include \"pod5_format/signal_compression.h\"\n#include \"pod5_format/signal_table_reader.h\"\n#include \"pod5_format/signal_table_writer.h\"\n#include \"pod5_format/types.h\"\n#include \"pod5_format/uuid.h\"\n#include \"pod5_format/version.h\"\n#include \"test_utils.h\"\n#include \"utils.h\"\n\n#include <arrow/array/array_nested.h>\n#include <arrow/array/array_primitive.h>\n#include <arrow/io/file.h>\n#include <arrow/memory_pool.h>\n#include <arrow/record_batch.h>\n#include <catch2/catch.hpp>\n\n#include <numeric>\n\nSCENARIO(\"Signal table Tests\")\n{\n    using namespace pod5;\n\n    (void)pod5::register_extension_types();\n    auto fin = gsl::finally([] { (void)pod5::unregister_extension_types(); });\n\n    std::mt19937 gen{Catch::rngSeed()};\n    auto uuid_gen = pod5::UuidRandomGenerator{gen};\n\n    auto file_identifier = uuid_gen();\n\n    auto read_id_1 = uuid_gen();\n    auto read_id_2 = uuid_gen();\n    std::vector<std::int16_t> signal_1(100'000);\n    std::iota(signal_1.begin(), signal_1.end(), 0);\n    std::vector<std::int16_t> signal_2(10'000, 1);\n\n    GIVEN(\"A signal table writer\")\n    {\n        auto filename = \"./foo.pod5\";\n        auto pool = arrow::system_memory_pool();\n\n        auto signal_type = GENERATE(SignalType::UncompressedSignal, SignalType::VbzSignal);\n\n        {\n            auto file_out =\n                *pod5::AsyncOutputStream::make(filename, pod5::make_thread_pool(1), true);\n            auto schema_metadata = make_schema_key_value_metadata(\n                {file_identifier, \"test_software\", *parse_version_number(Pod5Version)});\n            REQUIRE_ARROW_STATUS_OK(schema_metadata);\n\n            auto writer =\n                pod5::make_signal_table_writer(file_out, *schema_metadata, 100, signal_type, pool);\n            REQUIRE_ARROW_STATUS_OK(writer);\n\n            WHEN(\"Writing a read\")\n            {\n                auto row_1 = writer->add_signal(read_id_1, gsl::make_span(signal_1));\n\n                auto row_2 = writer->add_signal(read_id_2, gsl::make_span(signal_2));\n\n                REQUIRE_ARROW_STATUS_OK(writer->close());\n\n                THEN(\"Read row ids are correct\")\n                {\n                    REQUIRE_ARROW_STATUS_OK(row_1);\n                    REQUIRE_ARROW_STATUS_OK(row_2);\n                    CHECK(*row_1 == 0);\n                    CHECK(*row_2 == 1);\n                }\n            }\n        }\n\n        auto file_in = arrow::io::ReadableFile::Open(filename, pool);\n        {\n            REQUIRE_ARROW_STATUS_OK(file_in);\n\n            auto reader = pod5::make_signal_table_reader(*file_in, 20, pool);\n            CAPTURE(reader);\n            REQUIRE_ARROW_STATUS_OK(reader);\n\n            auto metadata = reader->schema_metadata();\n            CHECK(metadata.file_identifier == file_identifier);\n            CHECK(metadata.writing_software == \"test_software\");\n            CHECK(metadata.writing_pod5_version == *parse_version_number(Pod5Version));\n\n            REQUIRE(reader->num_record_batches() == 1);\n            auto const record_batch_0 = reader->read_record_batch(0);\n            REQUIRE_ARROW_STATUS_OK(record_batch_0);\n            REQUIRE(record_batch_0->num_rows() == 2);\n\n            auto read_id = record_batch_0->read_id_column();\n            CHECK(read_id->length() == 2);\n            CHECK(read_id->Value(0) == read_id_1);\n            CHECK(read_id->Value(1) == read_id_2);\n\n            if (signal_type == SignalType::VbzSignal) {\n                auto signal = record_batch_0->vbz_signal_column();\n                CHECK(signal->length() == 2);\n\n                auto compare_compressed_signal =\n                    [&](gsl::span<std::uint8_t const> compressed_actual,\n                        std::vector<std::int16_t> const & expected) {\n                        auto decompressed =\n                            pod5::decompress_signal(compressed_actual, expected.size(), pool);\n                        REQUIRE_ARROW_STATUS_OK(decompressed);\n\n                        auto actual =\n                            gsl::make_span((*decompressed)->data(), (*decompressed)->size())\n                                .as_span<std::int16_t const>();\n                        CHECK(actual == gsl::make_span(expected));\n                    };\n\n                auto signal_typed = std::static_pointer_cast<VbzSignalArray>(signal);\n                compare_compressed_signal(signal_typed->Value(0), signal_1);\n                compare_compressed_signal(signal_typed->Value(1), signal_2);\n            } else if (signal_type == SignalType::UncompressedSignal) {\n                auto signal = record_batch_0->uncompressed_signal_column();\n                CHECK(signal->length() == 2);\n\n                auto signal_1_read =\n                    std::static_pointer_cast<arrow::Int16Array>(signal->value_slice(0));\n                std::vector<std::int16_t> stored_values_1(\n                    signal_1_read->raw_values(),\n                    signal_1_read->raw_values() + signal_1_read->length());\n                CHECK(stored_values_1 == signal_1);\n                auto signal_2_read =\n                    std::static_pointer_cast<arrow::Int16Array>(signal->value_slice(1));\n                std::vector<std::int16_t> stored_values_2(\n                    signal_2_read->raw_values(),\n                    signal_2_read->raw_values() + signal_2_read->length());\n                CHECK(stored_values_2 == signal_2);\n            } else {\n                FAIL(\"Unknown signal type\");\n            }\n\n            auto samples = record_batch_0->samples_column();\n            CHECK(samples->length() == 2);\n            CHECK(samples->Value(0) == signal_1.size());\n            CHECK(samples->Value(1) == signal_2.size());\n        }\n    }\n}\n"
  },
  {
    "path": "c++/test/svb16_scalar_tests.cpp",
    "content": "#include \"pod5_format/svb16/decode.hpp\"\n#include \"pod5_format/svb16/encode.hpp\"\n\n#include <catch2/catch.hpp>\n\n#include <algorithm>\n#include <cstdint>\n#include <limits>\n#include <random>\n#include <vector>\n\nusing Catch::Matchers::Equals;\n\ntemplate <typename Int16T, bool UseDelta, bool UseZigzag>\nvoid test_scalar_encode_scalar_decode()\n{\n    static constexpr uint32_t DATA_COUNT = 1024;\n    std::minstd_rand rng;\n\n    std::vector<Int16T> data(DATA_COUNT);\n    std::uniform_int_distribution<Int16T> dist{\n        std::numeric_limits<Int16T>::min(), std::numeric_limits<Int16T>::max()};\n    std::generate(data.begin(), data.end(), [&] { return dist(rng); });\n\n    std::vector<uint8_t> encoded(svb16_max_encoded_length(data.size()));\n    auto const encoded_count =\n        svb16::encode_scalar<Int16T, UseDelta, UseZigzag>(\n            data.data(), encoded.data(), encoded.data() + svb16_key_length(data.size()), DATA_COUNT)\n        - encoded.data();\n\n    CHECK(encoded_count <= svb16_max_encoded_length(data.size()));\n\n    std::vector<Int16T> decoded(DATA_COUNT);\n    auto const encoded_span = gsl::make_span(encoded);\n    auto const key_length = svb16_key_length(data.size());\n    auto const consumed = svb16::decode_scalar<Int16T, UseDelta, UseZigzag>(\n                              gsl::make_span(decoded),\n                              encoded_span.subspan(0, key_length),\n                              encoded_span.subspan(key_length))\n                          - encoded.data();\n\n    CHECK(consumed == encoded_count);\n\n    CHECK_THAT(decoded, Equals(data));\n}\n\nTEST_CASE(\"Scalar decode is inverse of scalar encode\", \"[scalar]\")\n{\n    SECTION(\"Unsigned, no delta, no zig-zag\")\n    {\n        test_scalar_encode_scalar_decode<uint16_t, false, false>();\n    }\n    SECTION(\"Signed, no delta, no zig-zag\")\n    {\n        test_scalar_encode_scalar_decode<int16_t, false, false>();\n    }\n    SECTION(\"Unsigned, delta, no zig-zag\")\n    {\n        test_scalar_encode_scalar_decode<uint16_t, true, false>();\n    }\n    SECTION(\"Signed, delta, no zig-zag\")\n    {\n        test_scalar_encode_scalar_decode<int16_t, true, false>();\n    }\n    SECTION(\"Unsigned, delta, zig-zag\")\n    {\n        test_scalar_encode_scalar_decode<uint16_t, true, true>();\n    }\n    SECTION(\"Signed, delta, zig-zag\") { test_scalar_encode_scalar_decode<int16_t, true, true>(); }\n    SECTION(\"Unsigned, no delta, zig-zag\")\n    {\n        // this scenario doesn't really make sense, but it's possible, so let's test it\n        test_scalar_encode_scalar_decode<uint16_t, false, true>();\n    }\n    SECTION(\"Signed, no delta, zig-zag\")\n    {\n        test_scalar_encode_scalar_decode<int16_t, false, true>();\n    }\n}\n"
  },
  {
    "path": "c++/test/svb16_x64_tests.cpp",
    "content": "#include \"pod5_format/svb16/decode.hpp\"\n#include \"pod5_format/svb16/encode.hpp\"\n\n#include <catch2/catch.hpp>\n\n#include <algorithm>\n#include <cstdint>\n#include <limits>\n#include <numeric>\n#include <random>\n#include <vector>\n\n#ifdef SVB16_X64\n\nusing Catch::Matchers::Equals;\n\ntemplate <typename Int16T, bool UseDelta, bool UseZigzag>\nvoid test_sse_encode_scalar_decode()\n{\n    uint32_t const DATA_COUNT = GENERATE(\n        1000,\n        20000);  // Deliberately not aligned to 64 so we test the scalar tidy up code at the end.\n    std::minstd_rand rng;\n    std::vector<Int16T> data(DATA_COUNT);\n    std::uniform_int_distribution<Int16T> dist{\n        std::numeric_limits<Int16T>::min(), std::numeric_limits<Int16T>::max()};\n    std::generate(data.begin(), data.end(), [&] { return dist(rng); });\n\n    std::vector<uint8_t> encoded(svb16_max_encoded_length(data.size()));\n    auto const encoded_count =\n        svb16::encode_sse<Int16T, UseDelta, UseZigzag>(\n            data.data(), encoded.data(), encoded.data() + svb16_key_length(data.size()), DATA_COUNT)\n        - encoded.data();\n\n    CHECK(encoded_count <= svb16_max_encoded_length(data.size()));\n\n    std::vector<uint8_t> encoded_scalar(svb16_max_encoded_length(data.size()));\n    auto const scalar_encoded_count = svb16::encode_scalar<Int16T, UseDelta, UseZigzag>(\n                                          data.data(),\n                                          encoded_scalar.data(),\n                                          encoded_scalar.data() + svb16_key_length(data.size()),\n                                          DATA_COUNT)\n                                      - encoded_scalar.data();\n    CHECK(scalar_encoded_count == encoded_count);\n    CHECK(encoded == encoded_scalar);\n\n    std::vector<Int16T> decoded(DATA_COUNT);\n    auto const encoded_span = gsl::make_span(encoded);\n    auto const key_length = svb16_key_length(data.size());\n    auto const consumed = svb16::decode_sse<Int16T, UseDelta, UseZigzag>(\n                              gsl::make_span(decoded),\n                              encoded_span.subspan(0, key_length),\n                              encoded_span.subspan(key_length))\n                          - encoded.data();\n\n    CHECK(consumed == encoded_count);\n\n    CHECK_THAT(decoded, Equals(data));\n}\n\nTEST_CASE(\"SSE decode is inverse of scalar encode\", \"[scalar]\")\n{\n    SECTION(\"Unsigned, no delta, no zig-zag\")\n    {\n        test_sse_encode_scalar_decode<uint16_t, false, false>();\n    }\n    SECTION(\"Signed, no delta, no zig-zag\")\n    {\n        test_sse_encode_scalar_decode<int16_t, false, false>();\n    }\n    SECTION(\"Unsigned, delta, no zig-zag\")\n    {\n        test_sse_encode_scalar_decode<uint16_t, true, false>();\n    }\n    SECTION(\"Signed, delta, no zig-zag\") { test_sse_encode_scalar_decode<int16_t, true, false>(); }\n    SECTION(\"Unsigned, delta, zig-zag\") { test_sse_encode_scalar_decode<uint16_t, true, true>(); }\n    SECTION(\"Signed, delta, zig-zag\") { test_sse_encode_scalar_decode<int16_t, true, true>(); }\n    SECTION(\"Unsigned, no delta, zig-zag\")\n    {\n        // this scenario doesn't really make sense, but it's possible, so let's test it\n        test_sse_encode_scalar_decode<uint16_t, false, true>();\n    }\n    SECTION(\"Signed, no delta, zig-zag\") { test_sse_encode_scalar_decode<int16_t, false, true>(); }\n}\n\n#endif\n"
  },
  {
    "path": "c++/test/test_utils.h",
    "content": "#pragma once\n\n#include <arrow/result.h>\n#include <arrow/status.h>\n#include <catch2/catch.hpp>\n\ntemplate <typename T>\nstruct Catch::StringMaker<arrow::Result<T>> {\n    static std::string convert(arrow::Result<T> const & value) { return value.status().ToString(); }\n};\n\ntemplate <bool CheckOk>\nclass IsStatusOk : public Catch::MatcherBase<arrow::Status> {\npublic:\n    IsStatusOk() = default;\n\n    bool match(arrow::Status const & status) const override { return status.ok() == CheckOk; }\n\n    virtual std::string describe() const override { return \"== arrow::Status::OK()\"; }\n};\n\ntemplate <bool CheckOk, typename T>\nclass IsResultOk : public Catch::MatcherBase<arrow::Result<T>> {\npublic:\n    IsResultOk() = default;\n\n    bool match(arrow::Result<T> const & status) const override { return status.ok() == CheckOk; }\n\n    virtual std::string describe() const override { return \"== arrow::Status::OK()\"; }\n};\n\ntemplate <typename T>\ninline IsResultOk<true, T> _is_arrow_ok(arrow::Result<T> const &)\n{\n    return IsResultOk<true, T>();\n}\n\ninline IsStatusOk<true> _is_arrow_ok(arrow::Status const &) { return IsStatusOk<true>(); }\n\ntemplate <typename T>\ninline IsResultOk<false, T> _is_arrow_not_ok(arrow::Result<T> const &)\n{\n    return IsResultOk<false, T>();\n}\n\ninline IsStatusOk<false> _is_arrow_not_ok(arrow::Status const &) { return IsStatusOk<false>(); }\n\n#define CHECK_ARROW_STATUS_OK(statement)      \\\n    do {                                      \\\n        auto const & _res = (statement);      \\\n        CHECK_THAT(_res, _is_arrow_ok(_res)); \\\n    } while (false)\n\n#define REQUIRE_ARROW_STATUS_OK(statement)      \\\n    do {                                        \\\n        auto const & _res = (statement);        \\\n        REQUIRE_THAT(_res, _is_arrow_ok(_res)); \\\n    } while (false)\n\n#define CHECK_ARROW_STATUS_NOT_OK(statement)      \\\n    do {                                          \\\n        auto const & _res = (statement);          \\\n        CHECK_THAT(_res, _is_arrow_not_ok(_res)); \\\n    } while (false)\n\n#define REQUIRE_ARROW_STATUS_NOT_OK(statement)      \\\n    do {                                            \\\n        auto const & _res = (statement);            \\\n        REQUIRE_THAT(_res, _is_arrow_not_ok(_res)); \\\n    } while (false)\n"
  },
  {
    "path": "c++/test/thread_pool_tests.cpp",
    "content": "#include \"pod5_format/thread_pool.h\"\n\n#include <catch2/catch.hpp>\n\n#include <condition_variable>\n#include <mutex>\n#include <thread>\n#include <vector>\n\nTEST_CASE(\"Thread pool runs tasks concurrently\", \"[thread_pool]\")\n{\n    using namespace std::chrono_literals;\n\n    auto const explicit_stop = GENERATE(true, false);\n    CAPTURE(explicit_stop);\n\n    auto const use_strands = GENERATE(true, false);\n    CAPTURE(use_strands);\n\n    // semaphores only in std lib in c++20, so fake them\n    std::mutex sem_mutex;\n    int sem1 = 2;\n    std::condition_variable cv1;\n    int sem2 = 2;\n    std::condition_variable cv2;\n\n    auto const create_task = [&]() -> std::function<void()> {\n        return [&] {\n            std::unique_lock<std::mutex> l{sem_mutex};\n            sem1--;\n            if (sem1 > 0) {\n                cv1.wait(l, [&] { return sem1 == 0; });\n            } else {\n                l.unlock();\n                cv1.notify_all();\n                std::this_thread::sleep_for(1ms);\n                l.lock();\n            }\n\n            sem2--;\n            if (sem2 > 0) {\n                cv2.wait(l, [&] { return sem2 == 0; });\n            } else {\n                l.unlock();\n                cv2.notify_all();\n            }\n        };\n    };\n\n    auto thread_pool = pod5::make_thread_pool(2);\n    std::shared_ptr<pod5::ThreadPoolStrand> strands[2];\n    if (use_strands) {\n        for (unsigned i = 0; i < 2; ++i) {\n            strands[i] = thread_pool->create_strand();\n            strands[i]->post(create_task());\n        }\n    } else {\n        thread_pool->post(create_task());\n        thread_pool->post(create_task());\n    }\n\n    if (explicit_stop) {\n        thread_pool->stop_and_drain();\n    } else {\n        thread_pool.reset();\n        for (unsigned i = 0; i < 2; ++i) {\n            strands[i].reset();\n        }\n    }\n\n    REQUIRE(sem1 == 0);\n    REQUIRE(sem2 == 0);\n}\n\nTEST_CASE(\"Tasks on the same strand are serialised\", \"[thread_pool]\")\n{\n    using namespace std::chrono_literals;\n\n    auto const explicit_stop = GENERATE(true, false);\n    CAPTURE(explicit_stop);\n\n    std::mutex seq_mutex;\n    std::vector<int> seq;\n    seq.reserve(4);\n\n    auto const create_task = [&](int const num) -> std::function<void()> {\n        return [&, num] {\n            {\n                std::lock_guard<std::mutex> l{seq_mutex};\n                seq.push_back(num);\n            }\n            std::this_thread::sleep_for(50ms);\n            {\n                std::lock_guard<std::mutex> l{seq_mutex};\n                seq.push_back(num);\n            }\n        };\n    };\n\n    auto thread_pool = pod5::make_thread_pool(2);\n    auto strand = thread_pool->create_strand();\n    strand->post(create_task(0));\n    strand->post(create_task(1));\n\n    if (explicit_stop) {\n        thread_pool->stop_and_drain();\n    } else {\n        thread_pool.reset();\n        strand.reset();\n    }\n\n    REQUIRE(seq.size() == 4);\n    if (seq[0] == 0) {\n        REQUIRE(seq == (std::vector<int>{0, 0, 1, 1}));\n    } else {\n        REQUIRE(seq == (std::vector<int>{1, 1, 0, 0}));\n    }\n}\n"
  },
  {
    "path": "c++/test/utils.h",
    "content": "#pragma once\n\n#include \"pod5_format/read_table_utils.h\"\n#include \"test_utils.h\"\n\n#include <arrow/result.h>\n#include <arrow/util/io_util.h>\n\ninline pod5::RunInfoData get_test_run_info_data(\n    std::string suffix = \"\",\n    std::int16_t adc_center_offset = 0,\n    std::int16_t sample_rate = 4000)\n{\n    return pod5::RunInfoData(\n        \"acquisition_id\" + suffix,\n        1005,\n        4095 + adc_center_offset,\n        -4096 + adc_center_offset,\n        {{\"context\" + suffix, \"tags\" + suffix},\n         {\"other\" + suffix, \"tagz\" + suffix},\n         {\"third\" + suffix, \"thing\" + suffix}},\n        \"experiment_name\" + suffix,\n        \"flow_cell_id\" + suffix,\n        \"flow_cell_product_code\" + suffix,\n        \"protocol_name\" + suffix,\n        \"protocol_run_id\" + suffix,\n        200005,\n        \"sample_id\" + suffix,\n        sample_rate,\n        \"sequencing_kit\" + suffix,\n        \"sequencer_position\" + suffix,\n        \"sequencer_position_type\" + suffix,\n        \"software\" + suffix,\n        \"system_name\" + suffix,\n        \"system_type\" + suffix,\n        {{\"tracking\" + suffix, \"id\" + suffix}});\n}\n\ninline arrow::Status remove_file_if_exists(std::string const & file)\n{\n    ARROW_ASSIGN_OR_RAISE(\n        auto arrow_reads_path, ::arrow::internal::PlatformFilename::FromString(file));\n    ARROW_ASSIGN_OR_RAISE(bool file_exists, arrow::internal::FileExists(arrow_reads_path));\n    if (file_exists) {\n        ARROW_RETURN_NOT_OK(arrow::internal::DeleteFile(arrow_reads_path));\n    }\n    return arrow::Status::OK();\n}\n"
  },
  {
    "path": "c++/test/uuid_tests.cpp",
    "content": "// This file contains code from https://github.com/mariusbancila/stduuid/ which has the following\n// license:\n//\n//   MIT License\n//\n//   Copyright (c) 2017\n//\n//   Permission is hereby granted, free of charge, to any person obtaining a copy\n//   of this software and associated documentation files (the \"Software\"), to deal\n//   in the Software without restriction, including without limitation the rights\n//   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n//   copies of the Software, and to permit persons to whom the Software is\n//   furnished to do so, subject to the following conditions:\n//\n//   The above copyright notice and this permission notice shall be included in all\n//   copies or substantial portions of the Software.\n//\n//   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n//   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n//   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n//   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n//   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n//   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n//   SOFTWARE.\n\n#include \"pod5_format/uuid.h\"\n\n#include <catch2/catch.hpp>\n\n#include <set>\n#include <unordered_set>\n\nTEST_CASE(\"Default constructor returns nil UUID\", \"[pod5::Uuid]\")\n{\n    pod5::Uuid nil;\n    REQUIRE(nil.is_nil());\n}\n\nTEST_CASE(\"Default constructor produces all-zero string\", \"[pod5::Uuid]\")\n{\n    pod5::Uuid nil;\n    REQUIRE(to_string(nil) == \"00000000-0000-0000-0000-000000000000\");\n    REQUIRE(pod5::to_string<wchar_t>(nil) == L\"00000000-0000-0000-0000-000000000000\");\n}\n\nTEST_CASE(\"Parsing the nil UUID is nil\", \"[pod5::Uuid]\")\n{\n    auto const no_braces = pod5::Uuid::from_string(\"00000000-0000-0000-0000-000000000000\");\n    auto const braces = pod5::Uuid::from_string(\"{00000000-0000-0000-0000-000000000000}\");\n    auto const no_braces_w = pod5::Uuid::from_string(L\"00000000-0000-0000-0000-000000000000\");\n    auto const braces_w = pod5::Uuid::from_string(L\"{00000000-0000-0000-0000-000000000000}\");\n\n    REQUIRE(no_braces);\n    REQUIRE(no_braces->is_nil());\n    REQUIRE(braces);\n    REQUIRE(braces->is_nil());\n    REQUIRE(no_braces_w);\n    REQUIRE(no_braces_w->is_nil());\n    REQUIRE(braces_w);\n    REQUIRE(braces_w->is_nil());\n}\n\nTEST_CASE(\"Parsing produces the same value with or without braces\", \"[pod5::Uuid]\")\n{\n    auto const no_braces = pod5::Uuid::from_string(\"1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c7\");\n    auto const braces = pod5::Uuid::from_string(\"{1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c7}\");\n    auto const no_braces_w = pod5::Uuid::from_string(L\"1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c7\");\n    auto const braces_w = pod5::Uuid::from_string(L\"{1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c7}\");\n\n    REQUIRE(no_braces == braces);\n    REQUIRE(no_braces_w == braces_w);\n}\n\nTEST_CASE(\"Parsing produces the same value from char or wchar_t\", \"[pod5::Uuid]\")\n{\n    auto const no_braces = pod5::Uuid::from_string(\"1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c7\");\n    auto const braces = pod5::Uuid::from_string(\"{1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c7}\");\n    auto const no_braces_w = pod5::Uuid::from_string(L\"1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c7\");\n    auto const braces_w = pod5::Uuid::from_string(L\"{1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c7}\");\n\n    REQUIRE(no_braces == no_braces_w);\n    REQUIRE(braces == braces_w);\n}\n\nTEST_CASE(\"A parsed UUID prints the same value\", \"[pod5::Uuid]\")\n{\n    auto const guid = pod5::Uuid::from_string(\"1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c7\");\n    REQUIRE(guid);\n    REQUIRE(to_string(*guid) == \"1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c7\");\n    REQUIRE(pod5::to_string<wchar_t>(*guid) == L\"1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c7\");\n}\n\nTEST_CASE(\"Invalid UUIDs cannot be parsed\", \"[pod5::Uuid]\")\n{\n    REQUIRE_FALSE(pod5::Uuid::from_string(\"\"));\n    REQUIRE_FALSE(pod5::Uuid::from_string(\"{}\"));\n    // mismatched braces\n    REQUIRE_FALSE(pod5::Uuid::from_string(\"{1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c7\"));\n    REQUIRE_FALSE(pod5::Uuid::from_string(\"1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c7}\"));\n    // missing a char\n    REQUIRE_FALSE(pod5::Uuid::from_string(\"1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c\"));\n    // too many chars\n    REQUIRE_FALSE(pod5::Uuid::from_string(\"1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96c77\"));\n    // invalid characters\n    REQUIRE_FALSE(pod5::Uuid::from_string(\"1d5a3dd9-2d50-4f2b-a0fb-a3a749eb96cg\"));\n}\n\nTEST_CASE(\"Construction from iterators\", \"[pod5::Uuid]\")\n{\n    using namespace std::string_literals;\n\n    {\n        std::array<uint8_t, 16> const arr{\n            {0x47,\n             0x18,\n             0x38,\n             0x23,\n             0x25,\n             0x74,\n             0x4b,\n             0xfd,\n             0xb4,\n             0x11,\n             0x99,\n             0xed,\n             0x17,\n             0x7d,\n             0x3e,\n             0x43}};\n\n        pod5::Uuid guid(arr.begin(), arr.end());\n        REQUIRE(to_string(guid) == \"47183823-2574-4bfd-b411-99ed177d3e43\"s);\n    }\n\n    {\n        uint8_t const arr[16] = {\n            0x47,\n            0x18,\n            0x38,\n            0x23,\n            0x25,\n            0x74,\n            0x4b,\n            0xfd,\n            0xb4,\n            0x11,\n            0x99,\n            0xed,\n            0x17,\n            0x7d,\n            0x3e,\n            0x43};\n\n        pod5::Uuid guid(std::begin(arr), std::end(arr));\n        REQUIRE(to_string(guid) == \"47183823-2574-4bfd-b411-99ed177d3e43\"s);\n    }\n}\n\nTEST_CASE(\"Construction from arrays\", \"[pod5::Uuid]\")\n{\n    using namespace std::string_literals;\n\n    {\n        pod5::Uuid guid{\n            {0x47,\n             0x18,\n             0x38,\n             0x23,\n             0x25,\n             0x74,\n             0x4b,\n             0xfd,\n             0xb4,\n             0x11,\n             0x99,\n             0xed,\n             0x17,\n             0x7d,\n             0x3e,\n             0x43}};\n\n        REQUIRE(to_string(guid) == \"47183823-2574-4bfd-b411-99ed177d3e43\"s);\n    }\n\n    {\n        std::array<uint8_t, 16> const arr{\n            {0x47,\n             0x18,\n             0x38,\n             0x23,\n             0x25,\n             0x74,\n             0x4b,\n             0xfd,\n             0xb4,\n             0x11,\n             0x99,\n             0xed,\n             0x17,\n             0x7d,\n             0x3e,\n             0x43}};\n\n        pod5::Uuid guid(arr);\n        REQUIRE(to_string(guid) == \"47183823-2574-4bfd-b411-99ed177d3e43\"s);\n    }\n\n    {\n        uint8_t const arr[16] = {\n            0x47,\n            0x18,\n            0x38,\n            0x23,\n            0x25,\n            0x74,\n            0x4b,\n            0xfd,\n            0xb4,\n            0x11,\n            0x99,\n            0xed,\n            0x17,\n            0x7d,\n            0x3e,\n            0x43};\n\n        pod5::Uuid guid(arr);\n        REQUIRE(to_string(guid) == \"47183823-2574-4bfd-b411-99ed177d3e43\"s);\n    }\n}\n\nTEST_CASE(\"Test equality\", \"[operators]\")\n{\n    pod5::Uuid empty;\n\n    auto engine = pod5::UuidRandomGenerator::engine_type{Catch::rngSeed()};\n    pod5::Uuid guid = pod5::UuidRandomGenerator{engine}();\n\n    REQUIRE(empty == empty);\n    REQUIRE(guid == guid);\n    REQUIRE(empty != guid);\n}\n\nTEST_CASE(\"Test comparison\", \"[operators]\")\n{\n    auto empty = pod5::Uuid{};\n\n    auto engine = pod5::UuidRandomGenerator::engine_type{Catch::rngSeed()};\n\n    pod5::UuidRandomGenerator gen{engine};\n    auto id = gen();\n\n    REQUIRE(empty < id);\n\n    std::set<pod5::Uuid> ids{pod5::Uuid{}, gen(), gen(), gen(), gen()};\n\n    REQUIRE(ids.size() == 5);\n    REQUIRE(ids.find(pod5::Uuid{}) != ids.end());\n}\n\nTEST_CASE(\"Test hashing\", \"[ops]\")\n{\n    using namespace std::string_literals;\n    auto str = \"47183823-2574-4bfd-b411-99ed177d3e43\"s;\n    auto guid = pod5::Uuid::from_string(str).value();\n\n    auto h1 = std::hash<std::string>{};\n    auto h2 = std::hash<pod5::Uuid>{};\n#ifdef UUID_HASH_STRING_BASED\n    REQUIRE(h1(str) == h2(guid));\n#else\n    REQUIRE(h1(str) != h2(guid));\n#endif\n\n    auto engine = pod5::UuidRandomGenerator::engine_type{Catch::rngSeed()};\n    pod5::UuidRandomGenerator gen{engine};\n\n    std::unordered_set<pod5::Uuid> ids{pod5::Uuid{}, gen(), gen(), gen(), gen()};\n\n    REQUIRE(ids.size() == 5);\n    REQUIRE(ids.find(pod5::Uuid{}) != ids.end());\n}\n\nTEST_CASE(\"Test swap\", \"[ops]\")\n{\n    pod5::Uuid empty;\n\n    auto engine = pod5::UuidRandomGenerator::engine_type{Catch::rngSeed()};\n    pod5::Uuid guid = pod5::UuidRandomGenerator{engine}();\n\n    REQUIRE(empty.is_nil());\n    REQUIRE_FALSE(guid.is_nil());\n\n    std::swap(empty, guid);\n\n    REQUIRE_FALSE(empty.is_nil());\n    REQUIRE(guid.is_nil());\n\n    empty.swap(guid);\n\n    REQUIRE(empty.is_nil());\n    REQUIRE_FALSE(guid.is_nil());\n}\n\nTEST_CASE(\"Test constexpr\", \"[const]\")\n{\n    constexpr pod5::Uuid empty;\n    static_assert(empty.is_nil());\n}\n\nTEST_CASE(\"Test size\", \"[operators]\") { REQUIRE(sizeof(pod5::Uuid) == 16); }\n"
  },
  {
    "path": "ci/docker/Dockerfile.conda",
    "content": "FROM condaforge/mambaforge:latest\nWORKDIR /\n"
  },
  {
    "path": "ci/docker/Dockerfile.py39.arm64",
    "content": "from git.oxfordnanolabs.local:4567/minknow/images/build-aarch64-gcc9\n\nRUN yum groupinstall \"Development Tools\" -y\nRUN yum install wget openssl-devel libffi-devel bzip2-devel -y\nRUN wget https://www.python.org/ftp/python/3.9.10/Python-3.9.10.tgz\nRUN tar xvf Python-*\nWORKDIR Python-3.9.10/\nRUN ./configure --enable-optimizations\nRUN make altinstall\nRUN rm /usr/bin/python3 && ln -s /usr/local/bin/python3.9 /usr/bin/python3\n\nWORKDIR /\n"
  },
  {
    "path": "ci/docker/Dockerfile.py39.x64",
    "content": "from git.oxfordnanolabs.local:4567/minknow/images/build-x86_64-gcc9\n\nRUN yum groupinstall \"Development Tools\" -y\nRUN yum install wget openssl-devel libffi-devel bzip2-devel -y\nRUN wget https://www.python.org/ftp/python/3.9.10/Python-3.9.10.tgz\nRUN tar xvf Python-*\nWORKDIR Python-3.9.10/\nRUN ./configure --enable-optimizations\nRUN make altinstall\nRUN rm /usr/bin/python3 && ln -s /usr/local/bin/python3.9 /usr/bin/python3\n\nWORKDIR /\n"
  },
  {
    "path": "ci/generate_coverage_report.sh",
    "content": "#!/bin/bash -e\n\n# Parse args.\nif [ $# -ne 1 ]; then\n    echo \"Usage: $0 build_dir\"\n    exit 1\nfi\nbuild_dir=$(realpath \"$1\")\n\n# Set up the venv.\necho \"Setting up venv\"\nif [ ! -e .coverage_venv ]; then\n    python3 -m venv .coverage_venv\nfi\n# shellcheck disable=SC1091 # \"Not following: .coverage_venv/bin/activate was not specified as input\"\nsource .coverage_venv/bin/activate\n# --cobertura support added in 5.1.\npip install -U 'gcovr>=5.1'\n\n# Determine the root of the project.\n# Note: shellcheck wants these split up into separate lines.\nproject_root=$(realpath \"$0\")\nproject_root=$(dirname \"${project_root}\")\nproject_root=$(dirname \"${project_root}\")\ncd \"${project_root}\"\n\ngcovr_args=(\n    # work around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68080\n    --gcov-ignore-parse-errors=negative_hits.warn\n    --filter \"${project_root}/c\\+\\+\"\n)\n\nfunction generate_coverage {\n    test_name=$1\n    regex=$2\n\n    echo \"Generating coverage report for ${test_name}\"\n\n    # Clear out old coverage info.\n    find \"${project_root}\" -name \"*.gcda\" -delete\n\n    # Run the test.\n    # shellcheck disable=SC2086 # the regex is intentionally split\n    ctest --test-dir \"${build_dir}\" ${regex}\n\n    # Generate the coverage report for this test.\n    gcovr \"${gcovr_args[@]}\" --cobertura \"${project_root}/coverage-report-${test_name}.xml\"\n    gcovr \"${gcovr_args[@]}\" --html-single-page --html-details \"${project_root}/coverage-report-${test_name}.html\"\n}\n\n# Generate a report for each test.\nfor test_name in $(ctest --test-dir \"${build_dir}\" -N | sed -rn 's/^ +Test +#[0-9]+: +(.*)$/\\1/p'); do\n    generate_coverage \"${test_name}\" \"-R ^${test_name}\\$\"\ndone\n\n# Generate a full coverage report too.\ngenerate_coverage \"all\" \"\"\n\n# CI wants to see a TOTAL line in order to report coverage, so give it the one from all tests.\n# gcovr only has a resolution of 1%, so do the calculation ourselves.\ngcovr \"${gcovr_args[@]}\" | grep TOTAL | awk '{print $1, $2, $3, 100 * $3 / $2 \"%\"}'\n"
  },
  {
    "path": "ci/get_tag_version.cmake",
    "content": "set(CANONICAL_TAG_BUILD TRUE)\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/../cmake/POD5Version.cmake\")\nmessage(\"${POD5_FULL_VERSION}\")\n"
  },
  {
    "path": "ci/gitlab-ci-common.yml",
    "content": "variables:\n    CONAN_USER: nanopore\n    CONAN_CHANNEL: stable\n    CONAN_REFERENCE: '.'\n    # Location of the .conan dir: having it in $CI_PROJECT_DIR makes it easy to grab the packages as\n    # artifacts, and putting it in a job-specific subdir allows multiple packages to be unpacked\n    # into a single upload job (otherwise the metadata.json files would overwrite each other)\n    CONAN_USER_HOME: '${CI_PROJECT_DIR}/${CI_JOB_ID}'\n    PACKAGES_PER_VERSION: 2\n    # can set this instead for the total number:\n    #EXPECTED_PACKAGE_COUNT: 2\n\nstages:\n    - build\n    - upload\n\nbefore_script:\n    - conan config install --verify-ssl=no \"${CONAN_CONFIG_URL}\"\n\n#\n# use the extends keyword to inherit the job templates defined below\n#\n\n\n.parallel-cppstd:\n    # A matrix definition to allow conan builds with different cppstd\n    parallel:\n        matrix:\n        - CONAN_PROFILE_CPPSTD: [17, 20]\n\n.tarball-package: &tarball-package\n    # gitlab-runner on Windows silently fails to archive files whose full path is longer than 260\n    # characters; the MSYS `tar` command is not subject to this limitation (providing Windows has\n    # been configured to allow long paths), so we tar up packages in the build job and untar them in\n    # the upload job.\n    #\n    # This also allows us to only archive the package we just built, and not any of its dependencies\n    # (because we can use `conan inspect` to find the name of the right packages).\n    - PACKAGE_DIR=\"${CONAN_USER_HOME#${PWD}/}/.conan/data/$(conan inspect --raw name ${CONAN_REFERENCE})\"\n    - echo \"Packing from $PACKAGE_DIR\"\n    - tar -czvf \"conan-${CI_JOB_ID}.tar.gz\" \"$PACKAGE_DIR\"/*/${CONAN_USER}/${CONAN_CHANNEL}/{package,metadata.json}\n    - rm -rf \"${CONAN_USER_HOME}/.conan\"\n\n.profile-variables: &profile-variables\n    # The caller (an individual package) should have set up either PROFILE_BASE or PROFILE_BASE_HOST\n    # and PROFILE_BASE_BUILD. We set variables so that both PROFILE_BASE_HOST and PROFILE_BASE_BUILD\n    # are defined correctly after this call, or exit.\n    - if [[ -n ${PROFILE_BASE} && ( -n ${PROFILE_BASE_HOST} || -n ${PROFILE_BASE_BUILD} ) ]]; then\n    -     echo \"Only one of PROFILE_BASE or (PROFILE_BASE_HOST and PROFILE_BASE_BUILD) should be defined\"\n    -     exit 1\n    - fi\n    - if [[ -n ${PROFILE_BASE} ]]; then\n    -     PROFILE_BASE_HOST=${PROFILE_BASE}\n    -     PROFILE_BASE_BUILD=${PROFILE_BASE}\n    - fi\n    - if [[ -z ${PROFILE_BASE_HOST} || -z ${PROFILE_BASE_BUILD} ]]; then\n    -     echo \"Both PROFILE_BASE_HOST and PROFILE_BASE_BUILD variables need to be defined\"\n    -     exit 1\n    - fi\n\n.build-package:\n    # The script builds all required conan packages. The caller needs to set up:\n    #     Either PROFILE_BASE or both PROFILE_BASE_HOST and PROFILE_BASE_BUILD\n    #     VERSIONS as an array if one or more version numbers are wanted.\n    #     EXTRA_CREATE_ARGS is passed to conan unchanged, if present.\n    #\n    # EXTRA_CREATE_ARGS is only used by libcurl, which builds the libcurl in parallel with c_ares\n    # set to True and to False.\n    #\n    #\n    # The after_script removes unneeded builds and sources and packages everything into a tarball,\n    # artifacts defines the name and path for build artifacts.\n    stage: build\n    variables:\n        # For Linux we need to tell arrow to not use boost.\n        EXTRA_CREATE_ARGS: \"-o arrow:with_boost=False -o arrow:with_thrift=False -o arrow:parquet=False -o arrow:with_zstd=True\"\n    script:\n        - *profile-variables\n        - |\n            if [[ -n ${VERSIONS} ]]; then\n                for version in ${VERSIONS}; do\n                    export CONAN_PROFILE_BUILD_TYPE=Debug\n                    conan create --profile:build ${PROFILE_BASE_BUILD} --profile:host ${PROFILE_BASE_HOST} ${CONAN_REFERENCE} ${version}@${CONAN_USER}/${CONAN_CHANNEL} ${EXTRA_CREATE_ARGS}\n                    export CONAN_PROFILE_BUILD_TYPE=Release\n                    conan create --profile:build ${PROFILE_BASE_BUILD} --profile:host ${PROFILE_BASE_HOST} ${CONAN_REFERENCE} ${version}@${CONAN_USER}/${CONAN_CHANNEL} ${EXTRA_CREATE_ARGS}\n                done\n            else\n                export CONAN_PROFILE_BUILD_TYPE=Debug\n                conan create --profile:build ${PROFILE_BASE_BUILD} --profile:host ${PROFILE_BASE_HOST}  ${CONAN_REFERENCE} ${CONAN_USER}/${CONAN_CHANNEL} ${EXTRA_CREATE_ARGS}\n                export CONAN_PROFILE_BUILD_TYPE=Release\n                conan create --profile:build ${PROFILE_BASE_BUILD} --profile:host ${PROFILE_BASE_HOST} ${CONAN_REFERENCE} ${CONAN_USER}/${CONAN_CHANNEL} ${EXTRA_CREATE_ARGS}\n            fi\n    after_script:\n        # Re-load the venv if it exists\n        - if ls .venv/*/activate >/dev/null 2>&1; then source .venv/*/activate; fi\n        - conan --version\n        # Avoid storing things on the CI node unnecessarily\n        - conan remove \"*\" --builds --src --force\n        - *tarball-package\n    artifacts:\n        name: \"${CI_PROJECT_NAME}-${CI_JOB_ID}\"\n        paths:\n            - 'conan-*.tar.gz'\n\n.build-package-win:\n    # Almost the same as build-package. Sets two additional variables CONAN_USER_HOME_SHORT and\n    # CONAN_USE_ALWAYS_SHORT_PATHS. \"script\" is exactly the same as for build-package. \"after_script\"\n    # does some additional processing needed for Windows between removing conan builds and sources,\n    # and creating tarballs.\n    extends: .build-package\n    variables:\n        # avoid interfering with the standard conan short-path directory\n        CONAN_USER_HOME_SHORT: 'c:\\.conan-tmp'\n        # we're nesting conan's data dir pretty deep, so build systems that would normally be ok can\n        # fail if we don't use short paths\n        CONAN_USE_ALWAYS_SHORT_PATHS: '1'\n        # We need to override arrow's boost 1.85.0 requirement to match the version we use internally.\n        EXTRA_CREATE_ARGS: \"-o arrow:with_thrift=False -o arrow:parquet=False --require=boost/1.86.0@ -o boost:without_locale=True\"\n\n    after_script:\n        # Avoid storing things on the CI node unnecessarily\n        - conan remove \"*\" --builds --src --force\n        # CONAN_USE_ALWAYS_SHORT_PATHS links paths deep in the data dir to dirs in c:\\.conan\n        # Resolve package links (so they can be gathered into artifacts):\n        - shopt -s nullglob # allow there to be nothing, eg: if CONAN_USE_ALWAYS_SHORT_PATHS is off\n        # MOVE_COMMAND can be set to, say, \"cp -r\" if necessary. Moving has been seen to fail for\n        # packages with executables (especially if those executables are run as part of the test\n        # package), such as protobuf.\n        - for link in ${CONAN_USER_HOME}/.conan/data/*/*/$CONAN_USER/$CONAN_CHANNEL/package/*/.conan_link; do\n            source=$(cat $link) && ${MOVE_COMMAND:-mv} $(cygpath \"$source\")/* $(dirname $link) && rm $link;\n          done\n        # Clean up the short_paths folder (even on failure):\n        - rm -rf \"/c/.conan-tmp\"\n        - *tarball-package\n\n# This can be used to override the script stage to build both static and shared versions of a\n# library. The \"conan create\" commands are duplicates with either -o ${PACKAGE}:shared=False or\n# -o ${PACKAGE}:shared=True added. Since this doesn't use \"extends\" the caller has to extend\n# either build-package or build-package-win as well.\n.build-shared-and-static:\n    script:\n        - *profile-variables\n        - PACKAGE=\"$(conan inspect --raw name .)\"\n        - if [[ -n ${VERSIONS} ]]; then\n        -   for version in ${VERSIONS}; do\n        -     export CONAN_PROFILE_BUILD_TYPE=Debug\n        -     conan create --profile:build ${PROFILE_BASE_BUILD}\n                --profile:host ${PROFILE_BASE_HOST} ${CONAN_REFERENCE}\n                ${version}@${CONAN_USER}/${CONAN_CHANNEL}\n                -o ${PACKAGE}:shared=False\n                ${EXTRA_CREATE_ARGS}\n        -     conan create --profile:build ${PROFILE_BASE_BUILD}\n                --profile:host ${PROFILE_BASE_HOST} ${CONAN_REFERENCE}\n                ${version}@${CONAN_USER}/${CONAN_CHANNEL}\n                -o ${PACKAGE}:shared=True\n                ${EXTRA_CREATE_ARGS}\n        -     export CONAN_PROFILE_BUILD_TYPE=Release\n        -     conan create --profile:build ${PROFILE_BASE_BUILD}\n                --profile:host ${PROFILE_BASE_HOST} ${CONAN_REFERENCE}\n                ${version}@${CONAN_USER}/${CONAN_CHANNEL}\n                -o ${PACKAGE}:shared=False\n                ${EXTRA_CREATE_ARGS}\n        -     conan create --profile:build ${PROFILE_BASE_BUILD}\n                --profile:host ${PROFILE_BASE_HOST} ${CONAN_REFERENCE}\n                ${version}@${CONAN_USER}/${CONAN_CHANNEL}\n                -o ${PACKAGE}:shared=True\n                ${EXTRA_CREATE_ARGS}\n        -   done\n        - else\n        -   export CONAN_PROFILE_BUILD_TYPE=Debug\n        -   conan create --profile:build ${PROFILE_BASE_BUILD}\n              --profile:host ${PROFILE_BASE_HOST} ${CONAN_REFERENCE}\n              ${CONAN_USER}/${CONAN_CHANNEL}\n              -o ${PACKAGE}:shared=False\n              ${EXTRA_CREATE_ARGS}\n        -   conan create --profile:build ${PROFILE_BASE_BUILD}\n              --profile:host ${PROFILE_BASE_HOST} ${CONAN_REFERENCE}\n              ${CONAN_USER}/${CONAN_CHANNEL}\n              -o ${PACKAGE}:shared=True\n              ${EXTRA_CREATE_ARGS}\n        -   export CONAN_PROFILE_BUILD_TYPE=Release\n        -   conan create --profile:build ${PROFILE_BASE_BUILD}\n              --profile:host ${PROFILE_BASE_HOST} ${CONAN_REFERENCE}\n              ${CONAN_USER}/${CONAN_CHANNEL}\n              -o ${PACKAGE}:shared=False\n              ${EXTRA_CREATE_ARGS}\n        -   conan create --profile:build ${PROFILE_BASE_BUILD}\n              --profile:host ${PROFILE_BASE_HOST} ${CONAN_REFERENCE}\n              ${CONAN_USER}/${CONAN_CHANNEL}\n              -o ${PACKAGE}:shared=True\n              ${EXTRA_CREATE_ARGS}\n        - fi\n\n.upload-package:\n    stage: upload\n    image: git.oxfordnanolabs.local:4567/traque/ont-docker-base/ont-base-python:3.8\n    tags:\n        - linux\n        - docker\n    before_script:\n        - echo -e \"\\e[0Ksection_start:`date +%s`:install_conan[collapsed=true]\\r\\e[0KInstalling conan\"\n        - pip install 'conan<2'\n        - echo -e \"\\e[0Ksection_end:`date +%s`:install_conan\\r\\e[0K\"\n    script:\n        # BSD tar (on macOS) puts some extra optional information into the tarballs that GNU tar\n        # complains about. --warning=no-unknown-keyword suppresses this.\n        - for tarball in conan-*.tar.gz; do tar --warning=no-unknown-keyword -xf \"$tarball\"; done\n        - for conan_dir in ./*/.conan; do\n        - job_dir=\"$(dirname \"$conan_dir\")\"\n        - echo -e \"\\e[0Ksection_start:`date +%s`:upload_package\\r\\e[0KUploading from $job_dir\"\n        - export CONAN_USER_HOME=\"$PWD/$job_dir\"\n        - conan config install --verify-ssl=no \"${CONAN_CONFIG_URL}\"\n        - if [[ -n ${VERSIONS} ]]; then\n        -     expected_recipe_count=$(echo ${VERSIONS} | wc -w)\n        -     for version in ${VERSIONS}; do\n        -         conan export ${CONAN_REFERENCE} ${version}@${CONAN_USER}/${CONAN_CHANNEL}\n        -     done\n        - else\n        -     expected_recipe_count=1\n        -     conan export ${CONAN_REFERENCE} ${CONAN_USER}/${CONAN_CHANNEL}\n        - fi\n        - PACKAGE=\"$(conan inspect --raw name ${CONAN_REFERENCE})\"\n        - recipes=\"$(conan search --raw \"${PACKAGE}/*@${CONAN_USER}/${CONAN_CHANNEL}\")\"\n        - recipe_count=\"$(echo $recipes | wc -w)\"\n        - package_count=0\n        - for recipe in $recipes; do\n        -   echo \"${recipe}:\"\n        -   conan search \"$recipe\"\n        -   package_count=$(($package_count + $(conan search \"$recipe\" | grep \"Package_ID:\" | wc -l)))\n        - done\n        - if [[ -z $EXPECTED_PACKAGE_COUNT ]]; then\n        -   EXPECTED_PACKAGE_COUNT=$((PACKAGES_PER_VERSION * expected_recipe_count))\n        - fi\n        - if [[ $recipe_count -ne $expected_recipe_count ]] || [[ $package_count -ne $EXPECTED_PACKAGE_COUNT ]]; then\n        -     echo \"Expected $expected_recipe_count recipe(s) with $EXPECTED_PACKAGE_COUNT package(s), got $recipe_count recipe(s) with $package_count package(s)\"\n        -     exit 1\n        - fi\n        # conan claims it should pick this information up automatically, given the variable names,\n        # but it doesn't seem to work if you don't do this:\n        - conan user -r ont-artifactory -p \"${CONAN_PASSWORD}\" \"${CONAN_LOGIN_USERNAME}\"\n        - EXTRA_ARGS=\n        - if [[ -z $DO_UPLOAD ]]; then\n        -   DO_UPLOAD=no\n        -   if [[ $CI_COMMIT_REF_NAME == stable/* ]] || [[ $CI_COMMIT_REF_NAME == release/* ]] || [[ $CI_COMMIT_REF_NAME == $STABLE_BRANCH_NAME ]]; then\n        -     DO_UPLOAD=yes\n        -   fi\n        - fi\n        - if [[ $DO_UPLOAD == \"yes\" ]]; then\n        -   EXTRA_ARGS=--force\n        - else\n        -   'echo \"WARNING: NOT uploading to artifactory for this branch\"'\n        -   EXTRA_ARGS=--skip-upload\n        - fi\n        - for recipe in $recipes; do\n        -   conan upload -r ont-artifactory --all --check --confirm ${EXTRA_ARGS} \"$recipe\"\n        - done\n        - echo -e \"\\e[0Ksection_end:`date +%s`:upload_package\\r\\e[0K\"\n        - done # for conan_dir\n\n#\n# Various setup methods. Each sets a number of relevant tags, and one or two variables: For\n# non-cross compiling one variable PROFILE_BASE is set with the name of a profile which will be\n# adapted by adding \"\" or \"\". For cross compiling two variables PROFILE_BASE_BUILD\n# for the profile of the build machine and PROFILE_BASE_HOST for the host machine are set.\n#\n\n#\n# Set up for Windows versions\n#\n.profile-windows-x86_64-vs2019:\n    # Set up for Windows x86 using VS 2019, using conan and the profile windows-x86_64-vs2019,\n    # adapted for debug and release. To be called from individual packages by using \"extends\".\n    tags:\n        - windows\n        - cmake\n        - VS2019\n        - conan\n    variables:\n        PROFILE_BASE: windows-x86_64-vs2019.jinja\n    parallel: !reference [.parallel-cppstd,parallel]\n\n.profile-windows-x86_64-vs2019-conan2:\n    # Set up for Windows x86 using VS 2019, using conan and the profile windows-x86_64-vs2019,\n    # adapted for debug and release. To be called from individual packages by using \"extends\".\n    tags:\n        - windows\n        - cmake\n        - VS2019\n        - conan\n    variables:\n        CMAKE_GENERATOR: \"Visual Studio 16 2019\"\n        PROFILE_BASE: windows-x86_64-vs2019.jinja\n        CMAKE_PRESET: \"conan2-windows-x86_64-vs2019-cppstd${CONAN_PROFILE_CPPSTD}-release\"\n\n#\n# Set up for MacOS versions\n#\n.profile-macos-aarch64-appleclang-15.0:\n    # Set up for MacOS arm 64 using clang 15.0, using conan and the profile\n    # macos-aarch64-appleclang-15.0, adapted for debug and release. To be called from individual\n    # packages by using \"extends\".\n    tags:\n        - osx_arm64\n        - xcode-15.3\n        - conan\n    variables:\n        PROFILE_BASE: macos-aarch64-appleclang-15.0.jinja\n    parallel: !reference [.parallel-cppstd,parallel]\n\n.profile-macos-aarch64-appleclang-16.0:\n    # Set up for MacOS arm 64 using clang 16.0, using conan and the profile\n    # macos-aarch64-appleclang-16.0, adapted for debug and release. To be called from individual\n    # packages by using \"extends\".\n    tags:\n        - osx_arm64\n        - xcode-16.1\n        - conan\n    variables:\n        PROFILE_BASE: macos-aarch64-appleclang-16.0.jinja\n    parallel: !reference [.parallel-cppstd,parallel]\n\n.profile-macos-aarch64-appleclang-15.0-conan2:\n    # Set up for MacOS arm 64 using clang 15.0, using conan and the profile\n    # macos-aarch64-appleclang-15.0, adapted for debug and release. To be called from individual\n    # packages by using \"extends\".\n    tags:\n        - osx_arm64\n        - xcode-15.3\n        - conan\n    variables:\n        PROFILE_BASE: macos-aarch64-appleclang-15.0.jinja\n        CMAKE_PRESET: \"conan2-macos-appleclang-15.0-aarch64-cppstd${CONAN_PROFILE_CPPSTD}-release\"\n\n.profile-macos-aarch64-appleclang-16.0-conan2:\n    # Set up for MacOS arm 64 using clang 16.0, using conan and the profile\n    # macos-aarch64-appleclang-16.0, adapted for debug and release. To be called from individual\n    # packages by using \"extends\".\n    tags:\n        - osx_arm64\n        - xcode-16.1\n        - conan\n    variables:\n        PROFILE_BASE: macos-aarch64-appleclang-16.0.jinja\n        CMAKE_PRESET: \"conan2-macos-appleclang-16.0-aarch64-cppstd${CONAN_PROFILE_CPPSTD}-release\"\n#\n# Set up for linux versions\n#\n.profile-linux-x86_64-gcc9:\n    # Set up for linux x86 using gcc9, using docker and the profile linux-x86_64-gcc9, adapted\n    # for debug and release. To be called from individual packages by using \"extends\".\n    #\n    # The docker image builds on CentOS 7 using devtoolset-9, for maximum compatibility. This means\n    # the compiled code will work on any Ubuntu distro from Xenial onwards (and most other\n    # still-supported Linux distros). Differences between GCC 9's libstdc++ and GCC 4.8's libstdc++\n    # are handled by a static library, so no special handling of libstdc++ is required.\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-x86_64-gcc9:latest\n    tags:\n        - linux_x86\n        - docker\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc9.jinja\n    parallel: !reference [.parallel-cppstd,parallel]\n\n.profile-linux-x86_64-gcc9-conan2:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-x86_64-gcc9:latest\n    tags:\n        - linux_x86\n        - docker\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc9.jinja\n        CMAKE_PRESET: \"conan2-linux-gcc9-x86_64-cppstd${CONAN_PROFILE_CPPSTD}-release\"\n\n\n.profile-linux-x86_64-gcc11:\n    # Set up for linux x86 using gcc11, using docker and the profile linux-aarch64-gcc11, adapted\n    # for debug and release. To be called from individual packages by using \"extends\".\n    #\n    # Note that the docker image uses a GCC 11 backport to Ubuntu Bionic. Compiled artifacts will\n    # be mostly compatible with Ubuntu Bionic and later, except that they will need the correct\n    # libstdc++ to be available. This can be achieved by installing libstdc++6 from the GCC 11\n    # backport (available in the ~ubuntu-toolchain-r/test PPA), or by otherwise shipping that\n    # version of libstdc++6 in a way that the software can find it.\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-x86_64-gcc11:latest\n    tags:\n        - linux_x86\n        - docker\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc11.jinja\n    parallel: !reference [.parallel-cppstd,parallel]\n\n.profile-linux-x86_64-gcc11-conan2:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-x86_64-gcc11:latest\n    tags:\n        - linux_x86\n        - docker\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc11.jinja\n        CMAKE_PRESET: \"conan2-linux-gcc11-x86_64-cppstd${CONAN_PROFILE_CPPSTD}-release\"\n\n.profile-linux-x86_64-gcc11-asan-static-conan2:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-x86_64-gcc11:latest\n    tags:\n        - linux_x86\n        - docker\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc11-asan-static.jinja\n        CMAKE_PRESET: \"conan2-linux-gcc11-asan-static-x86_64-cppstd${CONAN_PROFILE_CPPSTD}-release\"\n\n.profile-linux-x86_64-gcc11-usan-static-conan2:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-x86_64-gcc11:latest\n    tags:\n        - linux_x86\n        - docker\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc11-usan-static.jinja\n        CMAKE_PRESET: \"conan2-linux-gcc11-usan-static-x86_64-cppstd${CONAN_PROFILE_CPPSTD}-release\"\n\n.profile-linux-x86_64-gcc11-tsan-static-conan2:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-x86_64-gcc11:latest\n    tags:\n        - linux_x86\n        - docker\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc11-tsan-static.jinja\n        CMAKE_PRESET: \"conan2-linux-gcc11-tsan-static-x86_64-cppstd${CONAN_PROFILE_CPPSTD}-release\"\n\n.profile-linux-x86_64-gcc11-asan-static:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-x86_64-gcc11:latest\n    tags:\n        - linux_x86\n        - docker\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc11-asan-static.jinja\n    parallel: !reference [.parallel-cppstd,parallel]\n\n.profile-linux-x86_64-gcc11-ausan-static:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-x86_64-gcc11:latest\n    tags:\n        - linux_x86\n        - docker\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc11-ausan-static.jinja\n    parallel: !reference [.parallel-cppstd,parallel]\n\n.profile-linux-x86_64-gcc11-tsan-static:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-x86_64-gcc11:latest\n    tags:\n        - linux_x86\n        - docker\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc11-tsan-static.jinja\n    parallel: !reference [.parallel-cppstd,parallel]\n\n.profile-linux-x86_64-gcc11-usan-static:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-x86_64-gcc11:latest\n    tags:\n        - linux_x86\n        - docker\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc11-usan-static.jinja\n    parallel: !reference [.parallel-cppstd,parallel]\n\n.profile-linux-x86_64-gcc13:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-x86_64-gcc13:latest\n    tags:\n        - linux_x86\n        - docker\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc13.jinja\n    parallel: !reference [.parallel-cppstd,parallel]\n\n.profile-linux-x86_64-gcc13-conan2:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-x86_64-gcc13:latest\n    tags:\n        - linux_x86\n        - docker\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc13.jinja\n        CMAKE_PRESET: \"conan2-linux-gcc13-x86_64-cppstd${CONAN_PROFILE_CPPSTD}-release\"\n\n.profile-linux-x86_64-gcc13-asan-static:\n    extends: .profile-linux-x86_64-gcc13\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc13-asan-static.jinja\n\n.profile-linux-x86_64-gcc13-tsan-static:\n    extends: .profile-linux-x86_64-gcc13\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc13-tsan-static.jinja\n\n.profile-linux-x86_64-gcc13-usan-static:\n    extends: .profile-linux-x86_64-gcc13\n    variables:\n        PROFILE_BASE: linux-x86_64-gcc13-usan-static.jinja\n\n\n.profile-linux-aarch64-gcc9:\n    # Set up for linux arm64 using gcc9, using docker and the profile linux-aarch64-gcc9, adapted\n    # for debug and release. To be called from individual packages by using \"extends\".\n    #\n    # The docker image builds on CentOS 7 using devtoolset-9, for maximum compatibility. This means\n    # the compiled code will work on any Ubuntu distro from Xenial onwards (and most other\n    # still-supported Linux distros). Differences between GCC 9's libstdc++ and GCC 4.8's libstdc++\n    # are handled by a static library, so no special handling of libstdc++ is required.\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-aarch64-gcc9:latest\n    tags:\n        - linux_aarch64\n        - docker\n    variables:\n        PROFILE_BASE: linux-aarch64-gcc9.jinja\n    parallel: !reference [.parallel-cppstd,parallel]\n\n.profile-linux-aarch64-gcc9-conan2:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-aarch64-gcc9:latest\n    tags:\n        - linux_aarch64\n        - docker\n    variables:\n        PROFILE_BASE: linux-aarch64-gcc9.jinja\n        CMAKE_PRESET: \"conan2-linux-gcc9-aarch64-cppstd${CONAN_PROFILE_CPPSTD}-release\"\n\n.profile-linux-aarch64-gcc11:\n    # Set up for linux arm64 using gcc11, using docker and the profile linux-aarch64-gcc11, adapted\n    # for debug and release. To be called from individual packages by using \"extends\".\n    #\n    # Note that the docker image uses a GCC 11 backport to Ubuntu Bionic. Compiled artifacts will\n    # be mostly compatible with Ubuntu Bionic and later, except that they will need the correct\n    # libstdc++ to be available. This can be achieved by installing libstdc++6 from the GCC 11\n    # backport (available in the ~ubuntu-toolchain-r/test PPA), or by otherwise shipping that\n    # version of libstdc++6 in a way that the software can find it.\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-aarch64-gcc11:latest\n    tags:\n        - linux_aarch64\n        - docker\n    variables:\n        PROFILE_BASE: linux-aarch64-gcc11.jinja\n    parallel: !reference [.parallel-cppstd,parallel]\n\n.profile-linux-aarch64-gcc11-conan2:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-aarch64-gcc11:latest\n    tags:\n        - linux_aarch64\n        - docker\n    variables:\n        PROFILE_BASE: linux-aarch64-gcc11.jinja\n        CMAKE_PRESET: \"conan2-linux-gcc11-aarch64-cppstd${CONAN_PROFILE_CPPSTD}-release\"\n\n.profile-linux-aarch64-gcc13:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-aarch64-gcc13:latest\n    tags:\n        - linux_aarch64\n        - docker\n    variables:\n        PROFILE_BASE: linux-aarch64-gcc13.jinja\n    parallel: !reference [.parallel-cppstd,parallel]\n\n.profile-linux-aarch64-gcc13-conan2:\n    image: git.oxfordnanolabs.local:4567/informatics/conan-config/linux-aarch64-gcc13:latest\n    tags:\n        - linux_aarch64\n        - docker\n    variables:\n        PROFILE_BASE: linux-aarch64-gcc13.jinja\n        CMAKE_PRESET: \"conan2-linux-gcc13-aarch64-cppstd${CONAN_PROFILE_CPPSTD}-release\"\n"
  },
  {
    "path": "ci/install.sh",
    "content": "#!/bin/bash\n\nset -o errexit\nset -o pipefail\nset -o nounset\n# set -o xtrace\n\n# Tar up the archive build:\n(\n    cmake -DCMAKE_INSTALL_PREFIX=\"archive\" -DBUILD_TYPE=\"Release\" -DCOMPONENT=\"archive\" -P \"cmake_install.cmake\"\n    if [ \"$#\" -ge 1 ] && [ \"$1\" == \"STATIC_BUILD\" ]; then\n        if [[ \"$OSTYPE\" == \"linux-gnu\"* ]] && [[ -e \"archive/lib64\" ]]; then\n            cp \"../build/third_party/libs\"/* \"archive/lib64\"\n        else\n            cp \"../build/third_party/libs\"/* \"archive/lib\"\n        fi\n    fi\n)\n\n# Find the wheel:\n(\n    cmake -DCMAKE_INSTALL_PREFIX=\"wheel\" -DBUILD_TYPE=\"Release\" -DCOMPONENT=\"wheel\" -P \"cmake_install.cmake\"\n)\n"
  },
  {
    "path": "ci/package.sh",
    "content": "#!/bin/bash\n\nset -o errexit\nset -o pipefail\nset -o nounset\n# set -o xtrace\n\noutput_sku=$1\nauditwheel_platform=\nif [ $# -gt 1 ]; then\n    auditwheel_platform=\"${2}\"\nfi\n\nCURRENT_DIR=$(pwd)\n\nSCRIPT_DIR=$( cd -- \"$( dirname -- \"${BASH_SOURCE[0]}\" )\" &> /dev/null && pwd )\nREPO_ROOT=\"${SCRIPT_DIR}/../\"\n\ncd \"${REPO_ROOT}\"\npod5_version=\"$(cmake -P ci/get_tag_version.cmake 2>&1)\"\n\ncd \"${CURRENT_DIR}\"\n\n# Tar up the archive build:\n(\n    cd ./archive\n    tar -cvzf \"${REPO_ROOT}/lib_pod5-${pod5_version}-${output_sku}.tar.gz\" .\n)\n\n# Find the wheel:\n(\n    # Wheels are optional:\n    if [ -d \"wheel/\" ] ; then\n        cd wheel/\n        if [ -z \"${auditwheel_platform}\" ]; then\n            mv ./*.whl \"${REPO_ROOT}/\"\n        else\n            echo \"Running audit wheel\"\n            pwd\n            ls\n            auditwheel repair ./*.whl --plat \"${auditwheel_platform}\" -w \"${REPO_ROOT}/\"\n        fi\n    fi\n)\n"
  },
  {
    "path": "ci/unpack_libs_for_python.sh",
    "content": "#!/bin/bash\n\ninput_dir=$1\noutput_dir=$2\n\necho \"Unpacking builds from $input_dir to $output_dir\"\n\nfile_regex=\".*/lib_pod5-[0-9\\.]*-(.*).tar.gz\"\nfor i in \"${input_dir}\"/lib_pod5*.tar.gz; do\n\n    if [[ $i =~ $file_regex ]]\n    then\n        sku=\"${BASH_REMATCH[1]}\"\n        echo \"Extracting for SKU: $sku\"\n    else\n        echo \"$i doesn't match expected file pattern\" >&2\n        exit 1\n    fi\n\n    sku_out_dir=\"$output_dir/$sku/\"\n    mkdir -p \"${sku_out_dir}\"\n\n    tmp_dir=\"$output_dir/tmp\"\n    mkdir -p \"$tmp_dir\"\n    tar -xzf \"$i\" --directory \"$output_dir/tmp\"\n\n    mv \"${tmp_dir}\"/lib/* \"${sku_out_dir}\"\n\n    rm -r \"$tmp_dir\"\ndone\n\necho \"unpacked skus:\"\nls \"${output_dir}/\"\necho \"contents:\"\nls \"${output_dir}\"/*\n"
  },
  {
    "path": "cmake/BuildFlatBuffers.cmake",
    "content": "# Copyright 2015 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# General function to create FlatBuffer build rules for the given list of\n# schemas.\n#\n# flatbuffers_schemas: A list of flatbuffer schema files to process.\n#\n# schema_include_dirs: A list of schema file include directories, which will be\n# passed to flatc via the -I parameter.\n#\n# custom_target_name: The generated files will be added as dependencies for a\n# new custom target with this name. You should add that target as a dependency\n# for your main target to ensure these files are built. You can also retrieve\n# various properties from this target, such as GENERATED_INCLUDES_DIR,\n# BINARY_SCHEMAS_DIR, and COPY_TEXT_SCHEMAS_DIR.\n#\n# additional_dependencies: A list of additional dependencies that you'd like\n# all generated files to depend on. Pass in a blank string if you have none.\n#\n# generated_includes_dir: Where to generate the C++ header files for these\n# schemas. The generated includes directory will automatically be added to\n# CMake's include directories, and will be where generated header files are\n# placed. This parameter is optional; pass in empty string if you don't want to\n# generate include files for these schemas.\n#\n# binary_schemas_dir: If you specify an optional binary schema directory, binary\n# schemas will be generated for these schemas as well, and placed into the given\n# directory.\n#\n# copy_text_schemas_dir: If you want all text schemas (including schemas from\n# all schema include directories) copied into a directory (for example, if you\n# need them within your project to build JSON files), you can specify that\n# folder here. All text schemas will be copied to that folder.\n#\n# IMPORTANT: Make sure you quote all list arguments you pass to this function!\n# Otherwise CMake will only pass in the first element.\n# Example: build_flatbuffers(\"${fb_files}\" \"${include_dirs}\" target_name ...)\nfunction(build_flatbuffers flatbuffers_schemas\n                           schema_include_dirs\n                           custom_target_name\n                           additional_dependencies\n                           generated_includes_dir\n                           binary_schemas_dir\n                           copy_text_schemas_dir)\n\n  # Test if including from FindFlatBuffers\n  if(FLATBUFFERS_FLATC_EXECUTABLE)\n    set(FLATC_TARGET \"\")\n    set(FLATC ${FLATBUFFERS_FLATC_EXECUTABLE})\n  else()\n    set(FLATC_TARGET flatc)\n    set(FLATC flatc)\n  endif()\n  set(FLATC_SCHEMA_ARGS --gen-mutable)\n  if(FLATBUFFERS_FLATC_SCHEMA_EXTRA_ARGS)\n    set(FLATC_SCHEMA_ARGS\n      ${FLATBUFFERS_FLATC_SCHEMA_EXTRA_ARGS}\n      ${FLATC_SCHEMA_ARGS}\n      )\n  endif()\n\n  set(working_dir \"${CMAKE_CURRENT_SOURCE_DIR}\")\n\n  set(schema_glob \"*.fbs\")\n  # Generate the include files parameters.\n  set(include_params \"\")\n  set(all_generated_files \"\")\n  foreach (include_dir ${schema_include_dirs})\n    set(include_params -I ${include_dir} ${include_params})\n    if (NOT ${copy_text_schemas_dir} STREQUAL \"\")\n      # Copy text schemas from dependent folders.\n      file(GLOB_RECURSE dependent_schemas ${include_dir}/${schema_glob})\n      foreach (dependent_schema ${dependent_schemas})\n        file(COPY ${dependent_schema} DESTINATION ${copy_text_schemas_dir})\n      endforeach()\n    endif()\n  endforeach()\n\n  foreach(schema ${flatbuffers_schemas})\n    get_filename_component(filename ${schema} NAME_WE)\n    # For each schema, do the things we requested.\n    if (NOT ${generated_includes_dir} STREQUAL \"\")\n      set(generated_include ${generated_includes_dir}/${filename}_generated.h)\n      add_custom_command(\n        OUTPUT ${generated_include}\n        COMMAND ${FLATC} ${FLATC_SCHEMA_ARGS}\n        -o ${generated_includes_dir}\n        ${include_params}\n        -c ${schema}\n        DEPENDS ${FLATC_TARGET} ${schema} ${additional_dependencies}\n        WORKING_DIRECTORY \"${working_dir}\")\n      list(APPEND all_generated_files ${generated_include})\n    endif()\n\n    if (NOT ${binary_schemas_dir} STREQUAL \"\")\n      set(binary_schema ${binary_schemas_dir}/${filename}.bfbs)\n      add_custom_command(\n        OUTPUT ${binary_schema}\n        COMMAND ${FLATC} -b --schema\n        -o ${binary_schemas_dir}\n        ${include_params}\n        ${schema}\n        DEPENDS ${FLATC_TARGET} ${schema} ${additional_dependencies}\n        WORKING_DIRECTORY \"${working_dir}\")\n      list(APPEND all_generated_files ${binary_schema})\n    endif()\n\n    if (NOT ${copy_text_schemas_dir} STREQUAL \"\")\n      file(COPY ${schema} DESTINATION ${copy_text_schemas_dir})\n    endif()\n  endforeach()\n\n  # Create a custom target that depends on all the generated files.\n  # This is the target that you can depend on to trigger all these\n  # to be built.\n  add_custom_target(${custom_target_name}\n                    DEPENDS ${all_generated_files} ${additional_dependencies})\n\n  # Register the include directory we are using.\n  if (NOT ${generated_includes_dir} STREQUAL \"\")\n    include_directories(${generated_includes_dir})\n    set_property(TARGET ${custom_target_name}\n      PROPERTY GENERATED_INCLUDES_DIR\n      ${generated_includes_dir})\n  endif()\n\n  # Register the binary schemas dir we are using.\n  if (NOT ${binary_schemas_dir} STREQUAL \"\")\n    set_property(TARGET ${custom_target_name}\n      PROPERTY BINARY_SCHEMAS_DIR\n      ${binary_schemas_dir})\n  endif()\n\n  # Register the text schema copy dir we are using.\n  if (NOT ${copy_text_schemas_dir} STREQUAL \"\")\n    set_property(TARGET ${custom_target_name}\n      PROPERTY COPY_TEXT_SCHEMAS_DIR\n      ${copy_text_schemas_dir})\n  endif()\nendfunction()\n\n# Creates a target that can be linked against that generates flatbuffer headers.\n#\n# This function takes a target name and a list of schemas. You can also specify\n# other flagc flags using the FLAGS option to change the behavior of the flatc\n# tool.\n#\n# Arguments:\n#   TARGET: The name of the target to generate.\n#   SCHEMAS: The list of schema files to generate code for.\n#   BINARY_SCHEMAS_DIR: Optional. The directory in which to generate binary\n#       schemas. Binary schemas will only be generated if a path is provided.\n#   INCLUDE: Optional. Search for includes in the specified paths. (Use this\n#       instead of \"-I <path>\" and the FLAGS option so that CMake is aware of\n#       the directories that need to be searched).\n#   INCLUDE_PREFIX: Optional. The directory in which to place the generated\n#       files. Use this instead of the --include-prefix option.\n#   FLAGS: Optional. A list of any additional flags that you would like to pass\n#       to flatc.\n#\n# Example:\n#\n#     flatbuffers_generate_headers(\n#         TARGET my_generated_headers_target\n#         INCLUDE_PREFIX ${MY_INCLUDE_PREFIX}\"\n#         SCHEMAS ${MY_SCHEMA_FILES}\n#         BINARY_SCHEMAS_DIR \"${MY_BINARY_SCHEMA_DIRECTORY}\"\n#         FLAGS --gen-object-api)\n#\n#     target_link_libraries(MyExecutableTarget\n#         PRIVATE my_generated_headers_target\n#     )\nfunction(flatbuffers_generate_headers)\n  # Parse function arguments.\n  set(options)\n  set(one_value_args\n    \"TARGET\"\n    \"INCLUDE_PREFIX\"\n    \"BINARY_SCHEMAS_DIR\")\n  set(multi_value_args\n    \"SCHEMAS\"\n    \"INCLUDE\"\n    \"FLAGS\")\n  cmake_parse_arguments(\n    PARSE_ARGV 0\n    FLATBUFFERS_GENERATE_HEADERS\n    \"${options}\"\n    \"${one_value_args}\"\n    \"${multi_value_args}\")\n\n  # Test if including from FindFlatBuffers\n  if(FLATBUFFERS_FLATC_EXECUTABLE)\n    set(FLATC_TARGET \"\")\n    set(FLATC ${FLATBUFFERS_FLATC_EXECUTABLE})\n  else()\n    set(FLATC_TARGET flatc)\n    set(FLATC flatc)\n  endif()\n\n  set(working_dir \"${CMAKE_CURRENT_SOURCE_DIR}\")\n\n  # Generate the include files parameters.\n  set(include_params \"\")\n  foreach (include_dir ${FLATBUFFERS_GENERATE_HEADERS_INCLUDE})\n    set(include_params -I ${include_dir} ${include_params})\n  endforeach()\n\n  # Create a directory to place the generated code.\n  set(generated_target_dir \"${CMAKE_CURRENT_BINARY_DIR}/${FLATBUFFERS_GENERATE_HEADERS_TARGET}\")\n  set(generated_include_dir \"${generated_target_dir}\")\n  if (NOT ${FLATBUFFERS_GENERATE_HEADERS_INCLUDE_PREFIX} STREQUAL \"\")\n    set(generated_include_dir \"${generated_include_dir}/${FLATBUFFERS_GENERATE_HEADERS_INCLUDE_PREFIX}\")\n    list(APPEND FLATBUFFERS_GENERATE_HEADERS_FLAGS\n         \"--include-prefix\" ${FLATBUFFERS_GENERATE_HEADERS_INCLUDE_PREFIX})\n  endif()\n\n  # Create rules to generate the code for each schema.\n  foreach(schema ${FLATBUFFERS_GENERATE_HEADERS_SCHEMAS})\n    get_filename_component(filename ${schema} NAME_WE)\n    set(generated_include \"${generated_include_dir}/${filename}_generated.h\")\n\n    # Generate files for grpc if needed\n    set(generated_source_file)\n    if(\"${FLATBUFFERS_GENERATE_HEADERS_FLAGS}\" MATCHES \"--grpc\")\n      # Check if schema file contain a rpc_service definition\n      file(STRINGS ${schema} has_grpc REGEX \"rpc_service\")\n      if(has_grpc)\n        list(APPEND generated_include \"${generated_include_dir}/${filename}.grpc.fb.h\")\n        set(generated_source_file \"${generated_include_dir}/${filename}.grpc.fb.cc\")\n      endif()\n    endif()\n\n    add_custom_command(\n      OUTPUT ${generated_include} ${generated_source_file}\n      COMMAND ${FLATC} ${FLATC_ARGS}\n      -o ${generated_include_dir}\n      ${include_params}\n      -c ${schema}\n      ${FLATBUFFERS_GENERATE_HEADERS_FLAGS}\n      DEPENDS ${FLATC_TARGET} ${schema}\n      WORKING_DIRECTORY \"${working_dir}\"\n      COMMENT \"Building ${schema} flatbuffers...\")\n    list(APPEND all_generated_header_files ${generated_include})\n    list(APPEND all_generated_source_files ${generated_source_file})\n\n    # Generate the binary flatbuffers schemas if instructed to.\n    if (NOT ${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR} STREQUAL \"\")\n      set(binary_schema\n          \"${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR}/${filename}.bfbs\")\n      add_custom_command(\n        OUTPUT ${binary_schema}\n        COMMAND ${FLATC} -b --schema\n        -o ${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR}\n        ${include_params}\n        ${schema}\n        DEPENDS ${FLATC_TARGET} ${schema}\n        WORKING_DIRECTORY \"${working_dir}\")\n      list(APPEND all_generated_binary_files ${binary_schema})\n    endif()\n  endforeach()\n\n  # Set up interface library\n  add_library(${FLATBUFFERS_GENERATE_HEADERS_TARGET} INTERFACE)\n  target_sources(\n    ${FLATBUFFERS_GENERATE_HEADERS_TARGET}\n    INTERFACE\n      ${all_generated_header_files}\n      ${all_generated_binary_files}\n      ${all_generated_source_files}\n      ${FLATBUFFERS_GENERATE_HEADERS_SCHEMAS})\n  add_dependencies(\n    ${FLATBUFFERS_GENERATE_HEADERS_TARGET}\n    ${FLATC}\n    ${FLATBUFFERS_GENERATE_HEADERS_SCHEMAS})\n  target_include_directories(\n    ${FLATBUFFERS_GENERATE_HEADERS_TARGET}\n    INTERFACE ${generated_target_dir})\n\n  # Organize file layout for IDEs.\n  source_group(\n    TREE \"${generated_target_dir}\"\n    PREFIX \"Flatbuffers/Generated/Headers Files\"\n    FILES ${all_generated_header_files})\n  source_group(\n    TREE \"${generated_target_dir}\"\n    PREFIX \"Flatbuffers/Generated/Source Files\"\n    FILES ${all_generated_source_files})\n  source_group(\n    TREE ${working_dir}\n    PREFIX \"Flatbuffers/Schemas\"\n    FILES ${FLATBUFFERS_GENERATE_HEADERS_SCHEMAS})\n  if (NOT ${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR} STREQUAL \"\")\n    source_group(\n      TREE \"${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR}\"\n      PREFIX \"Flatbuffers/Generated/Binary Schemas\"\n      FILES ${all_generated_binary_files})\n  endif()\nendfunction()\n\n# Creates a target that can be linked against that generates flatbuffer binaries\n# from json files.\n#\n# This function takes a target name and a list of schemas and Json files. You\n# can also specify other flagc flags and options to change the behavior of the\n# flatc compiler.\n#\n# Adding this target to your executable ensurses that the flatbuffer binaries\n# are compiled before your executable is run.\n#\n# Arguments:\n#   TARGET: The name of the target to generate.\n#   JSON_FILES: The list of json files to compile to flatbuffers binaries.\n#   SCHEMA: The flatbuffers schema of the Json files to be compiled.\n#   INCLUDE: Optional. Search for includes in the specified paths. (Use this\n#       instead of \"-I <path>\" and the FLAGS option so that CMake is aware of\n#       the directories that need to be searched).\n#   OUTPUT_DIR: The directly where the generated flatbuffers binaries should be\n#       placed.\n#   FLAGS: Optional. A list of any additional flags that you would like to pass\n#       to flatc.\n#\n# Example:\n#\n#     flatbuffers_generate_binary_files(\n#         TARGET my_binary_data\n#         SCHEMA \"${MY_SCHEMA_DIR}/my_example_schema.fbs\"\n#         JSON_FILES ${MY_JSON_FILES}\n#         OUTPUT_DIR \"${MY_BINARY_DATA_DIRECTORY}\"\n#         FLAGS --strict-json)\n#\n#     target_link_libraries(MyExecutableTarget\n#         PRIVATE my_binary_data\n#     )\nfunction(flatbuffers_generate_binary_files)\n  # Parse function arguments.\n  set(options)\n  set(one_value_args\n    \"TARGET\"\n    \"SCHEMA\"\n    \"OUTPUT_DIR\")\n  set(multi_value_args\n    \"JSON_FILES\"\n    \"INCLUDE\"\n    \"FLAGS\")\n  cmake_parse_arguments(\n    PARSE_ARGV 0\n    FLATBUFFERS_GENERATE_BINARY_FILES\n    \"${options}\"\n    \"${one_value_args}\"\n    \"${multi_value_args}\")\n\n  # Test if including from FindFlatBuffers\n  if(FLATBUFFERS_FLATC_EXECUTABLE)\n    set(FLATC_TARGET \"\")\n    set(FLATC ${FLATBUFFERS_FLATC_EXECUTABLE})\n  else()\n    set(FLATC_TARGET flatc)\n    set(FLATC flatc)\n  endif()\n\n  set(working_dir \"${CMAKE_CURRENT_SOURCE_DIR}\")\n\n  # Generate the include files parameters.\n  set(include_params \"\")\n  foreach (include_dir ${FLATBUFFERS_GENERATE_BINARY_FILES_INCLUDE})\n    set(include_params -I ${include_dir} ${include_params})\n  endforeach()\n\n  # Create rules to generate the flatbuffers binary for each json file.\n  foreach(json_file ${FLATBUFFERS_GENERATE_BINARY_FILES_JSON_FILES})\n    get_filename_component(filename ${json_file} NAME_WE)\n    set(generated_binary_file \"${FLATBUFFERS_GENERATE_BINARY_FILES_OUTPUT_DIR}/${filename}.bin\")\n    add_custom_command(\n      OUTPUT ${generated_binary_file}\n      COMMAND ${FLATC} ${FLATC_ARGS}\n      -o ${FLATBUFFERS_GENERATE_BINARY_FILES_OUTPUT_DIR}\n      ${include_params}\n      -b ${FLATBUFFERS_GENERATE_BINARY_FILES_SCHEMA} ${json_file}\n      ${FLATBUFFERS_GENERATE_BINARY_FILES_FLAGS}\n      DEPENDS ${FLATC_TARGET} ${json_file}\n      WORKING_DIRECTORY \"${working_dir}\"\n      COMMENT \"Building ${json_file} binary flatbuffers...\")\n      list(APPEND all_generated_binary_files ${generated_binary_file})\n  endforeach()\n\n  # Set up interface library\n  add_library(${FLATBUFFERS_GENERATE_BINARY_FILES_TARGET} INTERFACE)\n  target_sources(\n    ${FLATBUFFERS_GENERATE_BINARY_FILES_TARGET}\n    INTERFACE\n      ${all_generated_binary_files}\n      ${FLATBUFFERS_GENERATE_BINARY_FILES_JSON_FILES}\n      ${FLATBUFFERS_GENERATE_BINARY_FILES_SCHEMA})\n  add_dependencies(\n    ${FLATBUFFERS_GENERATE_BINARY_FILES_TARGET}\n    ${FLATC})\n\n  # Organize file layout for IDEs.\n  source_group(\n    TREE ${working_dir}\n    PREFIX \"Flatbuffers/JSON Files\"\n    FILES ${FLATBUFFERS_GENERATE_BINARY_FILES_JSON_FILES})\n  source_group(\n    TREE ${working_dir}\n    PREFIX \"Flatbuffers/Schemas\"\n    FILES ${FLATBUFFERS_GENERATE_BINARY_FILES_SCHEMA})\n  source_group(\n    TREE ${FLATBUFFERS_GENERATE_BINARY_FILES_OUTPUT_DIR}\n    PREFIX \"Flatbuffers/Generated/Binary Files\"\n    FILES ${all_generated_binary_files})\nendfunction()\n"
  },
  {
    "path": "cmake/Findzstd.cmake",
    "content": "find_path(ZSTD_INCLUDE_DIR\n    NAMES zstd.h\n    PATHS\n        ${CONAN_INCLUDE_DIRS_RELEASE}\n        ${CONAN_INCLUDE_DIRS_DEBUG}\n)\n\nset(ZSTD_NAMES zstd zstd_static)\nset(ZSTD_NAMES_DEBUG zstdd zstd_staticd)\n\nfind_library(ZSTD_LIBRARY_RELEASE\n    NAMES ${ZSTD_NAMES}\n    PATHS ${CONAN_LIB_DIRS_RELEASE}\n)\nfind_library(ZSTD_LIBRARY_DEBUG\n    NAMES\n        ${ZSTD_NAMES_DEBUG}\n        ${ZSTD_NAMES}\n    PATHS ${CONAN_LIB_DIRS_DEBUG}\n)\n\ninclude(SelectLibraryConfigurations)\nselect_library_configurations(ZSTD)\n\nif(ZSTD_INCLUDE_DIR AND EXISTS \"${ZSTD_INCLUDE_DIR}/zstd.h\")\n    file(STRINGS \"${ZSTD_INCLUDE_DIR}/zstd.h\" ZSTD_VERSION_MAJOR_LINE REGEX \"^#define ZSTD_VERSION_MAJOR.*$\")\n    file(STRINGS \"${ZSTD_INCLUDE_DIR}/zstd.h\" ZSTD_VERSION_MINOR_LINE REGEX \"^#define ZSTD_VERSION_MINOR.*$\")\n    file(STRINGS \"${ZSTD_INCLUDE_DIR}/zstd.h\" ZSTD_VERSION_RELEASE_LINE REGEX \"^#define ZSTD_VERSION_RELEASE.*$\")\n\n    string(REGEX REPLACE \"^.*ZSTD_VERSION_MAJOR *([0-9]+)$\" \"\\\\1\" ZSTD_VERSION_MAJOR \"${ZSTD_VERSION_MAJOR_LINE}\")\n    string(REGEX REPLACE \"^.*ZSTD_VERSION_MINOR *([0-9]+)$\" \"\\\\1\" ZSTD_VERSION_MINOR \"${ZSTD_VERSION_MINOR_LINE}\")\n    string(REGEX REPLACE \"^.*ZSTD_VERSION_RELEASE *([0-9]+)$\" \"\\\\1\" ZSTD_VERSION_RELEASE \"${ZSTD_VERSION_RELEASE_LINE}\")\n\n    set(ZSTD_VERSION_STRING \"${ZSTD_VERSION_MAJOR}.${ZSTD_VERSION_MINOR}.${ZSTD_VERSION_RELEASE}\")\nendif()\n\n# handle the QUIETLY and REQUIRED arguments and set ZLIB_FOUND to TRUE if\n# all listed variables are TRUE\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(zstd REQUIRED_VARS ZSTD_LIBRARY ZSTD_INCLUDE_DIR\n                                       VERSION_VAR ZSTD_VERSION_STRING)\n\n\nif (ZSTD_FOUND)\n    set(ZSTD_INCLUDE_DIRS ${ZSTD_INCLUDE_DIR})\n\n    if (NOT ZSTD_LIBRARIES)\n        set(ZSTD_LIBRARIES ${ZSTD_LIBRARY})\n    endif()\n\n    if (NOT TARGET zstd::zstd)\n        add_library(zstd::zstd UNKNOWN IMPORTED)\n        set_target_properties(zstd::zstd PROPERTIES\n            INTERFACE_INCLUDE_DIRECTORIES \"${ZSTD_INCLUDE_DIRS}\")\n\n        if(ZSTD_LIBRARY_RELEASE)\n            set_property(TARGET zstd::zstd APPEND PROPERTY\n                IMPORTED_CONFIGURATIONS RELEASE)\n            set_target_properties(zstd::zstd PROPERTIES\n                IMPORTED_LOCATION_RELEASE \"${ZSTD_LIBRARY_RELEASE}\")\n        endif()\n\n        if(ZSTD_LIBRARY_DEBUG)\n            set_property(TARGET zstd::zstd APPEND PROPERTY\n                IMPORTED_CONFIGURATIONS DEBUG)\n            set_target_properties(zstd::zstd PROPERTIES\n                IMPORTED_LOCATION_DEBUG \"${ZSTD_LIBRARY_DEBUG}\")\n        endif()\n\n        if(NOT ZSTD_LIBRARY_RELEASE AND NOT ZSTD_LIBRARY_DEBUG)\n            set_property(TARGET zstd::zstd APPEND PROPERTY\n                IMPORTED_LOCATION \"${ZSTD_LIBRARY}\")\n        endif()\n    endif()\nendif()\n"
  },
  {
    "path": "cmake/conan_provider.cmake",
    "content": "# The MIT License (MIT)\n#\n# Copyright (c) 2024 JFrog\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in all\n# copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nset(CONAN_MINIMUM_VERSION 2.0.5)\n\n# Create a new policy scope and set the minimum required cmake version so the\n# features behind a policy setting like if(... IN_LIST ...) behaves as expected\n# even if the parent project does not specify a minimum cmake version or a minimum\n# version less than this module requires (e.g. 3.0) before the first project() call.\n# (see: https://cmake.org/cmake/help/latest/variable/CMAKE_PROJECT_TOP_LEVEL_INCLUDES.html)\n#\n# The policy-affecting calls like cmake_policy(SET...) or `cmake_minimum_required` only\n# affects the current policy scope, i.e. between the PUSH and POP in this case.\n#\n# https://cmake.org/cmake/help/book/mastering-cmake/chapter/Policies.html#the-policy-stack\ncmake_policy(PUSH)\ncmake_minimum_required(VERSION 3.24)\n\n\nfunction(detect_os os os_api_level os_sdk os_subsystem os_version)\n    # it could be cross compilation\n    message(STATUS \"CMake-Conan: cmake_system_name=${CMAKE_SYSTEM_NAME}\")\n    if(CMAKE_SYSTEM_NAME AND NOT CMAKE_SYSTEM_NAME STREQUAL \"Generic\")\n        if(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n            set(${os} Macos PARENT_SCOPE)\n        elseif(CMAKE_SYSTEM_NAME STREQUAL \"QNX\")\n            set(${os} Neutrino PARENT_SCOPE)\n        elseif(CMAKE_SYSTEM_NAME STREQUAL \"CYGWIN\")\n            set(${os} Windows PARENT_SCOPE)\n            set(${os_subsystem} cygwin PARENT_SCOPE)\n        elseif(CMAKE_SYSTEM_NAME MATCHES \"^MSYS\")\n            set(${os} Windows PARENT_SCOPE)\n            set(${os_subsystem} msys2 PARENT_SCOPE)\n        elseif(CMAKE_SYSTEM_NAME STREQUAL \"Emscripten\")\n            # https://github.com/emscripten-core/emscripten/blob/4.0.6/cmake/Modules/Platform/Emscripten.cmake#L17C1-L17C34\n            set(${os} Emscripten PARENT_SCOPE)\n        else()\n            set(${os} ${CMAKE_SYSTEM_NAME} PARENT_SCOPE)\n        endif()\n        if(CMAKE_SYSTEM_NAME STREQUAL \"Android\")\n            if(DEFINED ANDROID_PLATFORM)\n                string(REGEX MATCH \"[0-9]+\" _os_api_level ${ANDROID_PLATFORM})\n            elseif(DEFINED CMAKE_SYSTEM_VERSION)\n                set(_os_api_level ${CMAKE_SYSTEM_VERSION})\n            endif()\n            message(STATUS \"CMake-Conan: android api level=${_os_api_level}\")\n            set(${os_api_level} ${_os_api_level} PARENT_SCOPE)\n        endif()\n        if(CMAKE_SYSTEM_NAME MATCHES \"Darwin|iOS|tvOS|watchOS\")\n            # CMAKE_OSX_SYSROOT contains the full path to the SDK for MakeFile/Ninja\n            # generators, but just has the original input string for Xcode.\n            if(NOT IS_DIRECTORY ${CMAKE_OSX_SYSROOT})\n                set(_os_sdk ${CMAKE_OSX_SYSROOT})\n            else()\n                if(CMAKE_OSX_SYSROOT MATCHES Simulator)\n                    set(apple_platform_suffix simulator)\n                else()\n                    set(apple_platform_suffix os)\n                endif()\n                if(CMAKE_OSX_SYSROOT MATCHES AppleTV)\n                    set(_os_sdk \"appletv${apple_platform_suffix}\")\n                elseif(CMAKE_OSX_SYSROOT MATCHES iPhone)\n                    set(_os_sdk \"iphone${apple_platform_suffix}\")\n                elseif(CMAKE_OSX_SYSROOT MATCHES Watch)\n                    set(_os_sdk \"watch${apple_platform_suffix}\")\n                endif()\n            endif()\n            if(DEFINED os_sdk)\n                message(STATUS \"CMake-Conan: cmake_osx_sysroot=${CMAKE_OSX_SYSROOT}\")\n                set(${os_sdk} ${_os_sdk} PARENT_SCOPE)\n            endif()\n            if(DEFINED CMAKE_OSX_DEPLOYMENT_TARGET)\n                message(STATUS \"CMake-Conan: cmake_osx_deployment_target=${CMAKE_OSX_DEPLOYMENT_TARGET}\")\n                set(${os_version} ${CMAKE_OSX_DEPLOYMENT_TARGET} PARENT_SCOPE)\n            endif()\n        endif()\n    endif()\nendfunction()\n\n\nfunction(detect_arch arch)\n    # CMAKE_OSX_ARCHITECTURES can contain multiple architectures, but Conan only supports one.\n    # Therefore this code only finds one. If the recipes support multiple architectures, the\n    # build will work. Otherwise, there will be a linker error for the missing architecture(s).\n    if(DEFINED CMAKE_OSX_ARCHITECTURES)\n        string(REPLACE \" \" \";\" apple_arch_list \"${CMAKE_OSX_ARCHITECTURES}\")\n        list(LENGTH apple_arch_list apple_arch_count)\n        if(apple_arch_count GREATER 1)\n            message(WARNING \"CMake-Conan: Multiple architectures detected, this will only work if Conan recipe(s) produce fat binaries.\")\n        endif()\n    endif()\n    if(CMAKE_SYSTEM_NAME MATCHES \"Darwin|iOS|tvOS|watchOS\" AND NOT CMAKE_OSX_ARCHITECTURES STREQUAL \"\")\n        set(host_arch ${CMAKE_OSX_ARCHITECTURES})\n    elseif(MSVC)\n        set(host_arch ${CMAKE_CXX_COMPILER_ARCHITECTURE_ID})\n    else()\n        set(host_arch ${CMAKE_SYSTEM_PROCESSOR})\n    endif()\n    if(host_arch MATCHES \"aarch64|arm64|ARM64\")\n        set(_arch armv8)\n    elseif(host_arch MATCHES \"armv7|armv7-a|armv7l|ARMV7\")\n        set(_arch armv7)\n    elseif(host_arch MATCHES armv7s)\n        set(_arch armv7s)\n    elseif(host_arch MATCHES \"i686|i386|X86\")\n        set(_arch x86)\n    elseif(host_arch MATCHES \"AMD64|amd64|x86_64|x64\")\n        set(_arch x86_64)\n    endif()\n    if(EMSCRIPTEN)\n        # https://github.com/emscripten-core/emscripten/blob/4.0.6/cmake/Modules/Platform/Emscripten.cmake#L294C1-L294C80\n        set(_arch wasm)\n    endif()\n    message(STATUS \"CMake-Conan: cmake_system_processor=${_arch}\")\n    set(${arch} ${_arch} PARENT_SCOPE)\nendfunction()\n\n\nfunction(detect_cxx_standard cxx_standard)\n    set(${cxx_standard} ${CMAKE_CXX_STANDARD} PARENT_SCOPE)\n    if(CMAKE_CXX_EXTENSIONS)\n        set(${cxx_standard} \"gnu${CMAKE_CXX_STANDARD}\" PARENT_SCOPE)\n    endif()\nendfunction()\n\n\nmacro(detect_gnu_libstdcxx)\n    # _conan_is_gnu_libstdcxx true if GNU libstdc++\n    check_cxx_source_compiles(\"\n    #include <cstddef>\n    #if !defined(__GLIBCXX__) && !defined(__GLIBCPP__)\n    static_assert(false);\n    #endif\n    int main(){}\" _conan_is_gnu_libstdcxx)\n\n    # _conan_gnu_libstdcxx_is_cxx11_abi true if C++11 ABI\n    check_cxx_source_compiles(\"\n    #include <string>\n    static_assert(sizeof(std::string) != sizeof(void*), \\\"using libstdc++\\\");\n    int main () {}\" _conan_gnu_libstdcxx_is_cxx11_abi)\n\n    set(_conan_gnu_libstdcxx_suffix \"\")\n    if(_conan_gnu_libstdcxx_is_cxx11_abi)\n        set(_conan_gnu_libstdcxx_suffix \"11\")\n    endif()\n    unset (_conan_gnu_libstdcxx_is_cxx11_abi)\nendmacro()\n\n\nmacro(detect_libcxx)\n    # _conan_is_libcxx true if LLVM libc++\n    check_cxx_source_compiles(\"\n    #include <cstddef>\n    #if !defined(_LIBCPP_VERSION)\n       static_assert(false);\n    #endif\n    int main(){}\" _conan_is_libcxx)\nendmacro()\n\n\nfunction(detect_lib_cxx lib_cxx)\n    if(CMAKE_SYSTEM_NAME STREQUAL \"Android\")\n        message(STATUS \"CMake-Conan: android_stl=${CMAKE_ANDROID_STL_TYPE}\")\n        set(${lib_cxx} ${CMAKE_ANDROID_STL_TYPE} PARENT_SCOPE)\n        return()\n    endif()\n\n    include(CheckCXXSourceCompiles)\n\n    if(CMAKE_CXX_COMPILER_ID MATCHES \"GNU\")\n        detect_gnu_libstdcxx()\n        set(${lib_cxx} \"libstdc++${_conan_gnu_libstdcxx_suffix}\" PARENT_SCOPE)\n    elseif(CMAKE_CXX_COMPILER_ID MATCHES \"AppleClang\")\n        set(${lib_cxx} \"libc++\" PARENT_SCOPE)\n    elseif(CMAKE_CXX_COMPILER_ID MATCHES \"Clang\" AND NOT CMAKE_SYSTEM_NAME MATCHES \"Windows\")\n        # Check for libc++\n        detect_libcxx()\n        if(_conan_is_libcxx)\n            set(${lib_cxx} \"libc++\" PARENT_SCOPE)\n            return()\n        endif()\n\n        # Check for libstdc++\n        detect_gnu_libstdcxx()\n        if(_conan_is_gnu_libstdcxx)\n            set(${lib_cxx} \"libstdc++${_conan_gnu_libstdcxx_suffix}\" PARENT_SCOPE)\n            return()\n        endif()\n\n        # TODO: it would be an error if we reach this point\n    elseif(CMAKE_CXX_COMPILER_ID MATCHES \"MSVC\")\n        # Do nothing - compiler.runtime and compiler.runtime_type\n        # should be handled separately: https://github.com/conan-io/cmake-conan/pull/516\n        return()\n    else()\n        # TODO: unable to determine, ask user to provide a full profile file instead\n    endif()\nendfunction()\n\n\nfunction(detect_compiler compiler compiler_version compiler_runtime compiler_runtime_type)\n    if(DEFINED CMAKE_CXX_COMPILER_ID)\n        set(_compiler ${CMAKE_CXX_COMPILER_ID})\n        set(_compiler_version ${CMAKE_CXX_COMPILER_VERSION})\n    else()\n        if(NOT DEFINED CMAKE_C_COMPILER_ID)\n            message(FATAL_ERROR \"C or C++ compiler not defined\")\n        endif()\n        set(_compiler ${CMAKE_C_COMPILER_ID})\n        set(_compiler_version ${CMAKE_C_COMPILER_VERSION})\n    endif()\n\n    message(STATUS \"CMake-Conan: CMake compiler=${_compiler}\")\n    message(STATUS \"CMake-Conan: CMake compiler version=${_compiler_version}\")\n\n    if(_compiler MATCHES MSVC)\n        set(_compiler \"msvc\")\n        string(SUBSTRING ${MSVC_VERSION} 0 3 _compiler_version)\n        # Configure compiler.runtime and compiler.runtime_type settings for MSVC\n        if(CMAKE_MSVC_RUNTIME_LIBRARY)\n            set(_msvc_runtime_library ${CMAKE_MSVC_RUNTIME_LIBRARY})\n        else()\n            set(_msvc_runtime_library MultiThreaded$<$<CONFIG:Debug>:Debug>DLL) # default value documented by CMake\n        endif()\n\n        set(_KNOWN_MSVC_RUNTIME_VALUES \"\")\n        list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreaded MultiThreadedDLL)\n        list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreadedDebug MultiThreadedDebugDLL)\n        list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreaded$<$<CONFIG:Debug>:Debug> MultiThreaded$<$<CONFIG:Debug>:Debug>DLL)\n\n        # only accept the 6 possible values, otherwise we don't don't know to map this\n        if(NOT _msvc_runtime_library IN_LIST _KNOWN_MSVC_RUNTIME_VALUES)\n            message(FATAL_ERROR \"CMake-Conan: unable to map MSVC runtime: ${_msvc_runtime_library} to Conan settings\")\n        endif()\n\n        # Runtime is \"dynamic\" in all cases if it ends in DLL\n        if(_msvc_runtime_library MATCHES \".*DLL$\")\n            set(_compiler_runtime \"dynamic\")\n        else()\n            set(_compiler_runtime \"static\")\n        endif()\n        message(STATUS \"CMake-Conan: CMake compiler.runtime=${_compiler_runtime}\")\n\n        # Only define compiler.runtime_type when explicitly requested\n        # If a generator expression is used, let Conan handle it conditional on build_type\n        if(NOT _msvc_runtime_library MATCHES \"<CONFIG:Debug>:Debug>\")\n            if(_msvc_runtime_library MATCHES \"Debug\")\n                set(_compiler_runtime_type \"Debug\")\n            else()\n                set(_compiler_runtime_type \"Release\")\n            endif()\n            message(STATUS \"CMake-Conan: CMake compiler.runtime_type=${_compiler_runtime_type}\")\n        endif()\n\n        unset(_KNOWN_MSVC_RUNTIME_VALUES)\n\n    elseif(_compiler MATCHES AppleClang)\n        set(_compiler \"apple-clang\")\n        string(REPLACE \".\" \";\" VERSION_LIST ${_compiler_version})\n        list(GET VERSION_LIST 0 _compiler_version)\n    elseif(_compiler MATCHES Clang)\n        set(_compiler \"clang\")\n        string(REPLACE \".\" \";\" VERSION_LIST ${_compiler_version})\n        list(GET VERSION_LIST 0 _compiler_version)\n    elseif(_compiler MATCHES GNU)\n        set(_compiler \"gcc\")\n        string(REPLACE \".\" \";\" VERSION_LIST ${_compiler_version})\n        list(GET VERSION_LIST 0 _compiler_version)\n    endif()\n\n    message(STATUS \"CMake-Conan: [settings] compiler=${_compiler}\")\n    message(STATUS \"CMake-Conan: [settings] compiler.version=${_compiler_version}\")\n    if (_compiler_runtime)\n        message(STATUS \"CMake-Conan: [settings] compiler.runtime=${_compiler_runtime}\")\n    endif()\n    if (_compiler_runtime_type)\n        message(STATUS \"CMake-Conan: [settings] compiler.runtime_type=${_compiler_runtime_type}\")\n    endif()\n\n    set(${compiler} ${_compiler} PARENT_SCOPE)\n    set(${compiler_version} ${_compiler_version} PARENT_SCOPE)\n    set(${compiler_runtime} ${_compiler_runtime} PARENT_SCOPE)\n    set(${compiler_runtime_type} ${_compiler_runtime_type} PARENT_SCOPE)\nendfunction()\n\n\nfunction(detect_build_type build_type)\n    get_property(multiconfig_generator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)\n    if(NOT multiconfig_generator)\n        # Only set when we know we are in a single-configuration generator\n        # Note: we may want to fail early if `CMAKE_BUILD_TYPE` is not defined\n        set(${build_type} ${CMAKE_BUILD_TYPE} PARENT_SCOPE)\n    endif()\nendfunction()\n\n\nmacro(set_conan_compiler_if_appleclang lang command output_variable)\n    if(CMAKE_${lang}_COMPILER_ID STREQUAL \"AppleClang\")\n        execute_process(COMMAND xcrun --find ${command}\n            OUTPUT_VARIABLE _xcrun_out OUTPUT_STRIP_TRAILING_WHITESPACE)\n        cmake_path(GET _xcrun_out PARENT_PATH _xcrun_toolchain_path)\n        cmake_path(GET CMAKE_${lang}_COMPILER PARENT_PATH _compiler_parent_path)\n        if (\"${_xcrun_toolchain_path}\" STREQUAL \"${_compiler_parent_path}\")\n            set(${output_variable} \"\")\n        endif()\n        unset(_xcrun_out)\n        unset(_xcrun_toolchain_path)\n        unset(_compiler_parent_path)\n    endif()\nendmacro()\n\n\nmacro(append_compiler_executables_configuration)\n    set(_conan_c_compiler \"\")\n    set(_conan_cpp_compiler \"\")\n    set(_conan_rc_compiler \"\")\n    set(_conan_compilers_list \"\")\n    if(CMAKE_C_COMPILER)\n        set(_conan_c_compiler \"\\\"c\\\":\\\"${CMAKE_C_COMPILER}\\\"\")\n        set_conan_compiler_if_appleclang(C cc _conan_c_compiler)\n        list(APPEND _conan_compilers_list ${_conan_c_compiler})\n    else()\n        message(WARNING \"CMake-Conan: The C compiler is not defined. \"\n                        \"Please define CMAKE_C_COMPILER or enable the C language.\")\n    endif()\n    if(CMAKE_CXX_COMPILER)\n        set(_conan_cpp_compiler \"\\\"cpp\\\":\\\"${CMAKE_CXX_COMPILER}\\\"\")\n        set_conan_compiler_if_appleclang(CXX c++ _conan_cpp_compiler)\n        list(APPEND _conan_compilers_list ${_conan_cpp_compiler})\n    else()\n        message(WARNING \"CMake-Conan: The C++ compiler is not defined. \"\n                        \"Please define CMAKE_CXX_COMPILER or enable the C++ language.\")\n    endif()\n    if(CMAKE_RC_COMPILER)\n        set(_conan_rc_compiler \"\\\"rc\\\":\\\"${CMAKE_RC_COMPILER}\\\"\")\n        list(APPEND _conan_compilers_list ${_conan_rc_compiler})\n        # Not necessary to warn if RC not defined\n    endif()\n    if(NOT \"x${_conan_compilers_list}\" STREQUAL \"x\")\n        string(REPLACE \";\" \",\" _conan_compilers_list \"${_conan_compilers_list}\")\n        string(APPEND profile \"tools.build:compiler_executables={${_conan_compilers_list}}\\n\")\n    endif()\n    unset(_conan_c_compiler)\n    unset(_conan_cpp_compiler)\n    unset(_conan_rc_compiler)\n    unset(_conan_compilers_list)\nendmacro()\n\n\nfunction(detect_host_profile output_file)\n    detect_os(os os_api_level os_sdk os_subsystem os_version)\n    detect_arch(arch)\n    detect_compiler(compiler compiler_version compiler_runtime compiler_runtime_type)\n    detect_cxx_standard(compiler_cppstd)\n    detect_lib_cxx(compiler_libcxx)\n    detect_build_type(build_type)\n\n    set(profile \"\")\n    string(APPEND profile \"[settings]\\n\")\n    if(arch)\n        string(APPEND profile arch=${arch} \"\\n\")\n    endif()\n    if(os)\n        string(APPEND profile os=${os} \"\\n\")\n    endif()\n    if(os_api_level)\n        string(APPEND profile os.api_level=${os_api_level} \"\\n\")\n    endif()\n    if(os_version)\n        string(APPEND profile os.version=${os_version} \"\\n\")\n    endif()\n    if(os_sdk)\n        string(APPEND profile os.sdk=${os_sdk} \"\\n\")\n    endif()\n    if(os_subsystem)\n        string(APPEND profile os.subsystem=${os_subsystem} \"\\n\")\n    endif()\n    if(compiler)\n        string(APPEND profile compiler=${compiler} \"\\n\")\n    endif()\n    if(compiler_version)\n        string(APPEND profile compiler.version=${compiler_version} \"\\n\")\n    endif()\n    if(compiler_runtime)\n        string(APPEND profile compiler.runtime=${compiler_runtime} \"\\n\")\n    endif()\n    if(compiler_runtime_type)\n        string(APPEND profile compiler.runtime_type=${compiler_runtime_type} \"\\n\")\n    endif()\n    if(compiler_cppstd)\n        string(APPEND profile compiler.cppstd=${compiler_cppstd} \"\\n\")\n    endif()\n    if(compiler_libcxx)\n        string(APPEND profile compiler.libcxx=${compiler_libcxx} \"\\n\")\n    endif()\n    if(build_type)\n        string(APPEND profile \"build_type=${build_type}\\n\")\n    endif()\n\n    if(NOT DEFINED output_file)\n        set(file_name \"${CMAKE_BINARY_DIR}/profile\")\n    else()\n        set(file_name ${output_file})\n    endif()\n\n    string(APPEND profile \"[conf]\\n\")\n    string(APPEND profile \"tools.cmake.cmaketoolchain:generator=${CMAKE_GENERATOR}\\n\")\n\n    # propagate compilers via profile\n    append_compiler_executables_configuration()\n\n    if(os STREQUAL \"Android\")\n        string(APPEND profile \"tools.android:ndk_path=${CMAKE_ANDROID_NDK}\\n\")\n    endif()\n\n    message(STATUS \"CMake-Conan: Creating profile ${file_name}\")\n    file(WRITE ${file_name} ${profile})\n    message(STATUS \"CMake-Conan: Profile: \\n${profile}\")\nendfunction()\n\n\nfunction(conan_profile_detect_default)\n    message(STATUS \"CMake-Conan: Checking if a default profile exists\")\n    execute_process(COMMAND ${CONAN_COMMAND} profile path default\n                    RESULT_VARIABLE return_code\n                    OUTPUT_VARIABLE conan_stdout\n                    ERROR_VARIABLE conan_stderr\n                    ECHO_ERROR_VARIABLE    # show the text output regardless\n                    ECHO_OUTPUT_VARIABLE\n                    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})\n    if(NOT ${return_code} EQUAL \"0\")\n        message(STATUS \"CMake-Conan: The default profile doesn't exist, detecting it.\")\n        execute_process(COMMAND ${CONAN_COMMAND} profile detect\n            RESULT_VARIABLE return_code\n            OUTPUT_VARIABLE conan_stdout\n            ERROR_VARIABLE conan_stderr\n            ECHO_ERROR_VARIABLE    # show the text output regardless\n            ECHO_OUTPUT_VARIABLE\n            WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})\n    endif()\nendfunction()\n\n\nfunction(conan_install)\n    cmake_parse_arguments(ARGS conan_args ${ARGN})\n    set(conan_output_folder ${CMAKE_BINARY_DIR}/conan)\n    # Invoke \"conan install\" with the provided arguments\n    set(conan_args ${conan_args} -of=${conan_output_folder})\n    message(STATUS \"CMake-Conan: conan install ${CMAKE_SOURCE_DIR} ${conan_args} ${ARGN}\")\n\n\n    # In case there was not a valid cmake executable in the PATH, we inject the\n    # same we used to invoke the provider to the PATH\n    if(DEFINED PATH_TO_CMAKE_BIN)\n        set(old_path $ENV{PATH})\n        set(ENV{PATH} \"$ENV{PATH}:${PATH_TO_CMAKE_BIN}\")\n    endif()\n\n    execute_process(COMMAND ${CONAN_COMMAND} install ${CMAKE_SOURCE_DIR} ${conan_args} ${ARGN} --format=json\n                    RESULT_VARIABLE return_code\n                    OUTPUT_VARIABLE conan_stdout\n                    ERROR_VARIABLE conan_stderr\n                    ECHO_ERROR_VARIABLE    # show the text output regardless\n                    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})\n\n    if(DEFINED PATH_TO_CMAKE_BIN)\n        set(ENV{PATH} \"${old_path}\")\n    endif()\n\n    if(NOT \"${return_code}\" STREQUAL \"0\")\n        message(FATAL_ERROR \"Conan install failed='${return_code}'\")\n    endif()\n\n    # the files are generated in a folder that depends on the layout used, if\n    # one is specified, but we don't know a priori where this is.\n    # TODO: this can be made more robust if Conan can provide this in the json output\n    string(JSON conan_generators_folder GET \"${conan_stdout}\" graph nodes 0 generators_folder)\n    cmake_path(CONVERT ${conan_generators_folder} TO_CMAKE_PATH_LIST conan_generators_folder)\n\n    message(STATUS \"CMake-Conan: CONAN_GENERATORS_FOLDER=${conan_generators_folder}\")\n    set_property(GLOBAL PROPERTY CONAN_GENERATORS_FOLDER \"${conan_generators_folder}\")\n    # reconfigure on conanfile changes\n    string(JSON conanfile GET \"${conan_stdout}\" graph nodes 0 label)\n    message(STATUS \"CMake-Conan: CONANFILE=${CMAKE_SOURCE_DIR}/${conanfile}\")\n    set_property(DIRECTORY ${CMAKE_SOURCE_DIR} APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS \"${CMAKE_SOURCE_DIR}/${conanfile}\")\n    # success\n    set_property(GLOBAL PROPERTY CONAN_INSTALL_SUCCESS TRUE)\n\nendfunction()\n\n\nfunction(conan_get_version conan_command conan_current_version)\n    execute_process(\n        COMMAND ${conan_command} --version\n        OUTPUT_VARIABLE conan_output\n        RESULT_VARIABLE conan_result\n        OUTPUT_STRIP_TRAILING_WHITESPACE\n    )\n    if(conan_result)\n        message(FATAL_ERROR \"CMake-Conan: Error when trying to run Conan\")\n    endif()\n\n    string(REGEX MATCH \"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\" conan_version ${conan_output})\n    set(${conan_current_version} ${conan_version} PARENT_SCOPE)\nendfunction()\n\n\nfunction(conan_version_check)\n    set(options )\n    set(one_value_args MINIMUM CURRENT)\n    set(multi_value_args )\n    cmake_parse_arguments(conan_version_check\n        \"${options}\" \"${one_value_args}\" \"${multi_value_args}\" ${ARGN})\n\n    if(NOT conan_version_check_MINIMUM)\n        message(FATAL_ERROR \"CMake-Conan: Required parameter MINIMUM not set!\")\n    endif()\n        if(NOT conan_version_check_CURRENT)\n        message(FATAL_ERROR \"CMake-Conan: Required parameter CURRENT not set!\")\n    endif()\n\n    if(conan_version_check_CURRENT VERSION_LESS conan_version_check_MINIMUM)\n        message(FATAL_ERROR \"CMake-Conan: Conan version must be ${conan_version_check_MINIMUM} or later\")\n    endif()\nendfunction()\n\n\nmacro(construct_profile_argument argument_variable profile_list)\n    set(${argument_variable} \"\")\n    if(\"${profile_list}\" STREQUAL \"CONAN_HOST_PROFILE\")\n        set(_arg_flag \"--profile:host=\")\n    elseif(\"${profile_list}\" STREQUAL \"CONAN_BUILD_PROFILE\")\n        set(_arg_flag \"--profile:build=\")\n    endif()\n\n    set(_profile_list \"${${profile_list}}\")\n    list(TRANSFORM _profile_list REPLACE \"auto-cmake\" \"${CMAKE_BINARY_DIR}/conan_host_profile\")\n    list(TRANSFORM _profile_list PREPEND ${_arg_flag})\n    set(${argument_variable} ${_profile_list})\n\n    unset(_arg_flag)\n    unset(_profile_list)\nendmacro()\n\n\nmacro(conan_provide_dependency method package_name)\n    set_property(GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED TRUE)\n    get_property(_conan_install_success GLOBAL PROPERTY CONAN_INSTALL_SUCCESS)\n    if(NOT _conan_install_success)\n        find_program(CONAN_COMMAND \"conan\" REQUIRED)\n        conan_get_version(${CONAN_COMMAND} CONAN_CURRENT_VERSION)\n        conan_version_check(MINIMUM ${CONAN_MINIMUM_VERSION} CURRENT ${CONAN_CURRENT_VERSION})\n        message(STATUS \"CMake-Conan: first find_package() found. Installing dependencies with Conan\")\n        if(\"default\" IN_LIST CONAN_HOST_PROFILE OR \"default\" IN_LIST CONAN_BUILD_PROFILE)\n            conan_profile_detect_default()\n        endif()\n        if(\"auto-cmake\" IN_LIST CONAN_HOST_PROFILE)\n            detect_host_profile(${CMAKE_BINARY_DIR}/conan_host_profile)\n        endif()\n        construct_profile_argument(_host_profile_flags CONAN_HOST_PROFILE)\n        construct_profile_argument(_build_profile_flags CONAN_BUILD_PROFILE)\n        if(EXISTS \"${CMAKE_SOURCE_DIR}/conanfile.py\")\n            file(READ \"${CMAKE_SOURCE_DIR}/conanfile.py\" outfile)\n            if(NOT \"${outfile}\" MATCHES \".*CMakeDeps.*\")\n                message(WARNING \"Cmake-conan: CMakeDeps generator was not defined in the conanfile\")\n            endif()\n            set(generator \"\")\n        elseif (EXISTS \"${CMAKE_SOURCE_DIR}/conanfile.txt\")\n            file(READ \"${CMAKE_SOURCE_DIR}/conanfile.txt\" outfile)\n            if(NOT \"${outfile}\" MATCHES \".*CMakeDeps.*\")\n                message(WARNING \"Cmake-conan: CMakeDeps generator was not defined in the conanfile. \"\n                        \"Please define the generator as it will be mandatory in the future\")\n            endif()\n            set(generator \"-g;CMakeDeps\")\n        endif()\n        get_property(_multiconfig_generator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)\n        if(NOT _multiconfig_generator)\n            message(STATUS \"CMake-Conan: Installing single configuration ${CMAKE_BUILD_TYPE}\")\n            conan_install(${_host_profile_flags} ${_build_profile_flags} ${CONAN_INSTALL_ARGS} ${generator})\n        else()\n            message(STATUS \"CMake-Conan: Installing both Debug and Release\")\n            conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Release ${CONAN_INSTALL_ARGS} ${generator})\n            conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Debug ${CONAN_INSTALL_ARGS} ${generator})\n        endif()\n        unset(_host_profile_flags)\n        unset(_build_profile_flags)\n        unset(_multiconfig_generator)\n        unset(_conan_install_success)\n    else()\n        message(STATUS \"CMake-Conan: find_package(${ARGV1}) found, 'conan install' already ran\")\n        unset(_conan_install_success)\n    endif()\n\n    get_property(_conan_generators_folder GLOBAL PROPERTY CONAN_GENERATORS_FOLDER)\n\n    # Ensure that we consider Conan-provided packages ahead of any other,\n    # irrespective of other settings that modify the search order or search paths\n    # This follows the guidelines from the find_package documentation\n    #  (https://cmake.org/cmake/help/latest/command/find_package.html):\n    #       find_package (<PackageName> PATHS paths... NO_DEFAULT_PATH)\n    #       find_package (<PackageName>)\n\n    # Filter out `REQUIRED` from the argument list, as the first call may fail\n    set(_find_args_${package_name} \"${ARGN}\")\n    list(REMOVE_ITEM _find_args_${package_name} \"REQUIRED\")\n    if(NOT \"MODULE\" IN_LIST _find_args_${package_name})\n        find_package(${package_name} ${_find_args_${package_name}} BYPASS_PROVIDER PATHS \"${_conan_generators_folder}\" NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)\n        unset(_find_args_${package_name})\n    endif()\n\n    # Invoke find_package a second time - if the first call succeeded,\n    # this will simply reuse the result. If not, fall back to CMake default search\n    # behaviour, also allowing modules to be searched.\n    if(NOT ${package_name}_FOUND)\n        list(FIND CMAKE_MODULE_PATH \"${_conan_generators_folder}\" _index)\n        if(_index EQUAL -1)\n            list(PREPEND CMAKE_MODULE_PATH \"${_conan_generators_folder}\")\n        endif()\n        unset(_index)\n        find_package(${package_name} ${ARGN} BYPASS_PROVIDER)\n        list(REMOVE_ITEM CMAKE_MODULE_PATH \"${_conan_generators_folder}\")\n    endif()\nendmacro()\n\n\ncmake_language(\n    SET_DEPENDENCY_PROVIDER conan_provide_dependency\n    SUPPORTED_METHODS FIND_PACKAGE\n)\n\n\nmacro(conan_provide_dependency_check)\n    set(_conan_provide_dependency_invoked FALSE)\n    get_property(_conan_provide_dependency_invoked GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED)\n    if(NOT _conan_provide_dependency_invoked)\n        message(WARNING \"Conan is correctly configured as dependency provider, \"\n                        \"but Conan has not been invoked. Please add at least one \"\n                        \"call to `find_package()`.\")\n        if(DEFINED CONAN_COMMAND)\n            # suppress warning in case `CONAN_COMMAND` was specified but unused.\n            set(_conan_command ${CONAN_COMMAND})\n            unset(_conan_command)\n        endif()\n    endif()\n    unset(_conan_provide_dependency_invoked)\nendmacro()\n\n\n# Add a deferred call at the end of processing the top-level directory\n# to check if the dependency provider was invoked at all.\ncmake_language(DEFER DIRECTORY \"${CMAKE_SOURCE_DIR}\" CALL conan_provide_dependency_check)\n\n# Configurable variables for Conan profiles\nset(CONAN_HOST_PROFILE \"default;auto-cmake\" CACHE STRING \"Conan host profile\")\nset(CONAN_BUILD_PROFILE \"default\" CACHE STRING \"Conan build profile\")\nset(CONAN_INSTALL_ARGS \"--build=missing\" CACHE STRING \"Command line arguments for conan install\")\n\nfind_program(_cmake_program NAMES cmake NO_PACKAGE_ROOT_PATH NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH NO_CMAKE_FIND_ROOT_PATH)\nif(NOT _cmake_program)\n    get_filename_component(PATH_TO_CMAKE_BIN \"${CMAKE_COMMAND}\" DIRECTORY)\n    set(PATH_TO_CMAKE_BIN \"${PATH_TO_CMAKE_BIN}\" CACHE INTERNAL \"Path where the CMake executable is\")\nendif()\n\ncmake_policy(POP)\n"
  },
  {
    "path": "cmake/pod5_fuzz.cmake",
    "content": "if (NOT CMAKE_CXX_COMPILER_ID MATCHES \"Clang\")\n    message(FATAL_ERROR\n        \"Only LLVM based compilers are supported for fuzzing. Assuming that \"\n        \"'clang' is install, it can be picked by setting the environment \"\n        \"variables 'CC=clang' and 'CXX=clang++' before invoking cmake.\"\n    )\nendif()\n\n# Build everything with fuzzing instrumentation and sanitizers\nset(POD5_SANITIZER_FLAGS -fsanitize=address,undefined,fuzzer-no-link)\nadd_compile_options(-g ${POD5_SANITIZER_FLAGS} -UNDEBUG -O1)\nadd_link_options(${POD5_SANITIZER_FLAGS})\n"
  },
  {
    "path": "cmake/pod5_packaging.cmake",
    "content": "\nset(CPACK_PACKAGE_NAME \"lib_pod5\")\nset(CPACK_PACKAGE_VENDOR \"Oxford Nanopore\")\nset(CPACK_VERBATIM_VARIABLES true)\nset(CPACK_PACKAGE_VERSION_MAJOR ${POD5_VERSION_MAJOR})\nset(CPACK_PACKAGE_VERSION_MINOR ${POD5_VERSION_MINOR})\nset(CPACK_PACKAGE_VERSION_PATCH ${POD5_VERSION_REV})\nset(CPACK_RESOURCE_FILE_LICENSE \"${CMAKE_SOURCE_DIR}/LICENSE.md\")\n\ninclude(CPack)\n"
  },
  {
    "path": "cmake/presets/conan-build-options.json",
    "content": "{\n    \"version\": 4,\n    \"configurePresets\": [\n        {\n            \"name\": \"conan2-debug\",\n            \"binaryDir\": \"${sourceDir}/build\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CMAKE_BUILD_TYPE\": \"Debug\"\n            },\n            \"environment\": {\n                \"CONAN_PROFILE_BUILD_TYPE\": \"Debug\"\n            }\n        },\n        {\n            \"name\": \"conan2-release\",\n            \"binaryDir\": \"${sourceDir}/build\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CMAKE_BUILD_TYPE\": \"Release\"\n            },\n            \"environment\": {\n                \"CONAN_PROFILE_BUILD_TYPE\": \"Release\"\n            }\n        },\n        {\n            \"name\": \"conan2-cppstd20\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CMAKE_CXX_STANDARD\": \"20\"\n            },\n            \"environment\": {\n                \"CONAN_PROFILE_CPPSTD\": \"20\"\n            }\n        },\n        {\n            \"name\": \"conan2-cppstd17\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CMAKE_CXX_STANDARD\": \"17\"\n            },\n            \"environment\": {\n                \"CONAN_PROFILE_CPPSTD\": \"17\"\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "cmake/presets/conan-profiles.json",
    "content": "{\n    \"version\": 4,\n    \"configurePresets\": [\n        {\n            \"name\": \"conan2-gcc9-x86_64-profile\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CONAN_HOST_PROFILE\": \"linux-x86_64-gcc9.jinja\",\n                \"CONAN_BUILD_PROFILE\": \"linux-x86_64-gcc9.jinja\"\n            }\n        },\n        {\n            \"name\": \"conan2-gcc11-x86_64-profile\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CONAN_HOST_PROFILE\": \"linux-x86_64-gcc11.jinja\",\n                \"CONAN_BUILD_PROFILE\": \"linux-x86_64-gcc11.jinja\"\n            }\n        },\n        {\n            \"name\": \"conan2-gcc11-asan-static-x86_64-profile\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CONAN_HOST_PROFILE\": \"linux-x86_64-gcc11-asan-static.jinja\",\n                \"CONAN_BUILD_PROFILE\": \"linux-x86_64-gcc11-asan-static.jinja\"\n            }\n        },\n        {\n            \"name\": \"conan2-gcc11-usan-static-x86_64-profile\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CONAN_HOST_PROFILE\": \"linux-x86_64-gcc11-usan-static.jinja\",\n                \"CONAN_BUILD_PROFILE\": \"linux-x86_64-gcc11-usan-static.jinja\"\n            }\n        },\n        {\n            \"name\": \"conan2-gcc11-tsan-static-x86_64-profile\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CONAN_HOST_PROFILE\": \"linux-x86_64-gcc11-tsan-static.jinja\",\n                \"CONAN_BUILD_PROFILE\": \"linux-x86_64-gcc11-tsan-static.jinja\"\n            }\n        },\n        {\n            \"name\": \"conan2-gcc13-x86_64-profile\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CONAN_HOST_PROFILE\": \"linux-x86_64-gcc13.jinja\",\n                \"CONAN_BUILD_PROFILE\": \"linux-x86_64-gcc13.jinja\"\n            }\n        },\n        {\n            \"name\": \"conan2-gcc13-aarch64-profile\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CONAN_HOST_PROFILE\": \"linux-aarch64-gcc13.jinja\",\n                \"CONAN_BUILD_PROFILE\": \"linux-aarch64-gcc13.jinja\"\n            }\n        },\n        {\n            \"name\": \"conan2-gcc11-aarch64-profile\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CONAN_HOST_PROFILE\": \"linux-aarch64-gcc11.jinja\",\n                \"CONAN_BUILD_PROFILE\": \"linux-aarch64-gcc11.jinja\"\n            }\n        },\n        {\n            \"name\": \"conan2-gcc9-aarch64-profile\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CONAN_HOST_PROFILE\": \"linux-aarch64-gcc9.jinja\",\n                \"CONAN_BUILD_PROFILE\": \"linux-aarch64-gcc9.jinja\"\n            }\n        },\n        {\n            \"name\": \"conan2-appleclang-15.0-aarch64-profile\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CONAN_HOST_PROFILE\": \"macos-aarch64-appleclang-15.0.jinja\",\n                \"CONAN_BUILD_PROFILE\": \"macos-aarch64-appleclang-15.0.jinja\"\n            }\n        },\n        {\n            \"name\": \"conan2-appleclang-16.0-aarch64-profile\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CONAN_HOST_PROFILE\": \"macos-aarch64-appleclang-16.0.jinja\",\n                \"CONAN_BUILD_PROFILE\": \"macos-aarch64-appleclang-16.0.jinja\"\n            }\n        },\n        {\n            \"name\": \"conan2-windows-x86_64-vs2019-profile\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CONAN_HOST_PROFILE\": \"windows-x86_64-vs2019.jinja\",\n                \"CONAN_BUILD_PROFILE\": \"windows-x86_64-vs2019.jinja\"\n            },\n            \"environment\": {\n                \"CMAKE_GENERATOR\": \"Visual Studio 16 2019\"\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "cmake/presets/conan-provider.json",
    "content": "{\n    \"version\": 4,\n    \"configurePresets\": [\n        {\n            \"name\": \"conan2-provider\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CMAKE_PROJECT_TOP_LEVEL_INCLUDES\": \"${sourceDir}/cmake/conan_provider.cmake\",\n                \"CONAN2\": \"ON\",\n                \"CONAN_INSTALL_ARGS\":\n                    \"--build=never;-o:a=arrow/*:with_boost=False;-o:a=arrow/*:with_thrift=False;-o:a=arrow/*:parquet=False;-o:a=arrow/*:with_zstd=True;\"\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "conanfile.py",
    "content": "from conan import ConanFile\nfrom conan.tools.cmake import CMakeToolchain, CMake, CMakeDeps\nfrom conan.tools.files import collect_libs, copy\nfrom conan.tools.build import cross_building\nfrom conan.tools.cmake import cmake_layout\nimport os\n\n\nclass Pod5Conan(ConanFile):\n    name = \"pod5_file_format\"\n    license = \"MPL 2.0\"\n    url = \"https://github.com/nanoporetech/pod5-file-format\"\n    description = \"POD5 File format\"\n    topics = \"nanopore\", \"sequencing\", \"genomic\", \"dna\", \"arrow\"\n    settings = \"os\", \"compiler\", \"build_type\", \"arch\"\n    options = {\"shared\": [True, False]}\n    default_options = {\n        \"shared\": False,\n    }\n    exports_sources = [\n        \"c++/*\",\n        \"cmake/*\",\n        \"python/*\",\n        \"third_party/*\",\n        \"CMakeLists.txt\",\n        \"LICENSE.md\",\n    ]\n\n    \"\"\"\n    When building a static library, we need to pack arrow, zstd and if on linux jemalloc,\n    alongside pod5 static lib to avoid linking errors. This function copies those libs to\n    a folder called third_party in the build directory. The ci/install.sh ensures they end\n    up in the correct location to be deployed, if install is done via cmake.\n    \"\"\"\n\n    def _setup_third_party_deps_packaging(self):\n        deps_to_pack = (\n            [\"arrow\", \"zstd\", \"jemalloc\"]\n            if self.settings.os == \"Linux\"\n            else [\"arrow\", \"zstd\"]\n        )\n        static_lib_ext_wildcard = \"*.a\" if self.settings.os != \"Windows\" else \"*.lib\"\n        for dep in deps_to_pack:\n            if dep == \"jemalloc\":\n                static_lib_ext_wildcard = (\n                    \"*_pic.a\" if self.settings.os != \"Windows\" else \"*_pic.lib\"\n                )\n            dep_object = self.dependencies[dep]\n            copy(\n                self,\n                static_lib_ext_wildcard,\n                dep_object.cpp_info.libdir,\n                f\"{self.build_folder}/third_party/libs\",\n            )\n\n    def _licenses_path(self):\n        # This needs to match the install step inside CMake.\n        return os.path.join(self.build_folder, \"pod5_conan_licenses\")\n\n    def _copy_licenses(self):\n        # Copy each dependency's licenses.\n        for require, dependency in self.dependencies.items():\n            # package_folder will be None if this dependency isn't used.\n            if dependency.package_folder is not None:\n                copy(\n                    self,\n                    \"license*\",\n                    dependency.package_folder,\n                    os.path.join(self._licenses_path(), dependency.ref.name),\n                    ignore_case=True,\n                )\n\n    def layout(self):\n        cmake_layout(self, \"Ninja Multi-Config\")\n\n    def requirements(self):\n        self.requires(\"arrow/18.0.0\")\n        self.requires(\"flatbuffers/2.0.0\")\n        self.requires(\"zstd/[>=1.4.8 <=2.0.0]\")\n        self.requires(\"zlib/[>=1.2.11 <=2.0.0]\")\n        if not (\n            self.settings.os == \"Windows\"\n            or self.settings.os == \"Macos\"\n            or self.settings.os == \"iOS\"\n        ):\n            self.requires(\"jemalloc/5.2.1\")\n\n    \"\"\"\n    When cross compiling we need pre compiled flatbuffers for flatc to run on the build machine\n    which is not the target.\n    The flatbuffers version is most likely available already; it is on the master branch and\n    quite likely already built on the development branch. However, it seems that conan\n    doesn't realise this since it is the same package that it tries to build, even though it\n    is a different revision, flatbuffers on the other hand is downloaded.\n    \"\"\"\n\n    def build_requirements(self):\n        if hasattr(self, \"settings_build\") and cross_building(self):\n            # We are using an older version of flatbuffers not available on CCI.\n            # @TODO: Update to a version that exists in CCI\n            # When this line changes a corresponding change in .gitlab-ci.yml is required where this\n            # package is uninstalled.\n            self.tool_requires(\"flatbuffers/2.0.0\")\n\n    def generate(self):\n        if not self.options.shared:\n            self._setup_third_party_deps_packaging()\n\n        self._copy_licenses()\n\n        tc = CMakeToolchain(self)\n        tc.variables[\"ENABLE_CONAN\"] = \"ON\"\n        tc.variables[\"BUILD_PYTHON_WHEEL\"] = \"OFF\"\n        tc.variables[\"POD5_DISABLE_TESTS\"] = \"ON\"\n        tc.variables[\"POD5_BUILD_EXAMPLES\"] = \"OFF\"\n        tc.variables[\"BUILD_SHARED_LIB\"] = \"ON\" if self.options.shared else \"OFF\"\n\n        tc.generate()\n\n        deps = CMakeDeps(self)\n        deps.check_components_exist = True\n\n        # This ensures that target names in cmake would be in the form of libname::libname\n        deps.set_property(\"zstd\", \"cmake_target_name\", None)\n        deps.generate()\n\n    def build(self):\n        cmake = CMake(self)\n        cmake.configure()\n        cmake.build()\n\n    def package(self):\n        cmake = CMake(self)\n        cmake.install()\n\n        # Copy the license files\n        copy(\n            self,\n            \"*\",\n            self._licenses_path(),\n            os.path.join(self.package_folder, \"licenses\"),\n        )\n\n        # Package the required third party libs after installing pod5 static\n        if not self.options.shared:\n            src = f\"{self.build_folder}/third_party/libs/\"\n            dst = f\"{self.build_folder}/lib/\"\n            copy(self, \"*\", src, dst)\n\n    def package_info(self):\n        # Note: package_info collects information in self.cpp_info. It is called from the Conan\n        # application.\n        #\n        # This call is made immediately after the pre_package_info hook and before the\n        # post_package_info hook. To get more information, we can \"import traceback\" and \"import inspect\",\n        # then call traceback.print_stack() to print the complete call stack, or examine\n        # inspect.stack().\n        #\n        # The caller has created self.cpp_info with the name set to the name of self, with a rootpath,\n        # version and description from self, env_info and user_info set with default values,\n        # public_deps set to an array with the names of public requirements in conanfile.requires.items.\n\n        # Additions for this package. Note that everything in requirements needs to be mentioned\n        # here. Except for Windows and Macos, jemalloc is also needed.\n\n        self.cpp_info.libs = collect_libs(self)\n        self.cpp_info.requires = [\n            \"arrow::arrow\",\n            \"flatbuffers::flatbuffers\",\n            \"zstd::zstd\",\n            \"zlib::zlib\",\n        ]\n\n        # Workaround for broken Arrow package - ensure transitive includes are available\n        # Since our headers include Arrow headers, we need Arrow's includes to be transitively available\n        try:\n            arrow_dep = self.dependencies[\"arrow\"]\n            arrow_include_path = os.path.join(arrow_dep.package_folder, \"include\")\n            if os.path.exists(arrow_include_path):\n                self.cpp_info.includedirs.append(arrow_include_path)\n        except Exception:\n            # Arrow dependency not found or other issue - let it fail naturally\n            pass\n\n        # self.cpp\n        if self.settings.os == \"Linux\":\n            self.cpp_info.requires.append(\"jemalloc::jemalloc\")\n\n        if self.settings.os in [\"iOS\", \"Macos\"]:\n            self.cpp_info.frameworks = [\"CoreFoundation\"]\n"
  },
  {
    "path": "docs/DESIGN.md",
    "content": "POD5 File Format Design Details\n==============================\n\n## Summary\n\nThis file format has the following design goals (roughly in priority order):\n\n- Good write performance for MinKNOW\n- Recoverable if the writing process crashes\n- Good read performance for downstream tools, including basecall model generation\n- Efficient use of space\n- Straightforward to implement and maintain\n- Extensibility\n\nNote that trade-offs have been made between these goals, but we have mostly aimed to make those run-time decisions.\n\nWe have also chosen not to optimise for editing existing files.\n\n\n### Write performance\n\nThe aspects of this format that are designed to maximise write performance are:\n\n- Data can be written sequentially\n  - The sequential access pattern makes it easy to use efficient operating system APIs (such as io_uring on Linux)\n  - The sequential access pattern helps the operating system's I/O scheduler maximise throughput\n- Signal data from different reads can be interleaved, and data streams can be safely abandoned (at the cost of using more space than necessary)\n  - This allows MinKNOW to write out data as it arrives, potentially avoiding the need have an intermediate caching format (this file format can be used for the cache and the final output)\n- Support for space- and CPU-efficient compression routines (VBZ)\n  - This reduces the amount of data that needs to be written, which reduces I/O load\n\n### Recovery\n\nThe aspects of this format that are designed to allow for recovery if the writing process crashes are:\n\n- A way to indicate that a file is actually complete as intended (complete files end with a recognisable footer)\n- The Apache Feather format can be assembled by reading it sequentially, without using the footer\n- The data file format is append-only, which means that once data is recorded it cannot be corrupted by later updates\n\n### Read performance\n\nThe aspects of this format that are designed to maximise read performance are:\n\n- The Apache Feather format can be memory mapped and used directly\n- Apache Arrow has significant existing engineering work geared around efficient access to data, from the layout of the data itself to the library tooling\n- Storing direct information about signal data locations with the row table\n  - This allows quick access to a read's data without scanning the data file\n- It is possible to only decode part of a long read, due to read data being stored in chunks\n  - This is useful for model training\n- Read access does not require locking or otherwise modifying the file\n  - This allows multi-threaded and multi-process access to a file for reading\n\n### Efficient use of space\n\nThe aspects of this format that are designed to maximise use of space are:\n\n- Support for efficient compression routines (VBZ)\n- Apache Arrow's support for dictionary encoding\n- Apache Arrow's support for compressing buffers with standard compression routines\n\n### Ease of implementation\n\nThe aspects of this format that are designed to make the format easy to implement are:\n\n- Relying on an existing, widely-used format (Apache Arrow)\n\n### Extensibility\n\nThe aspects of this format that are designed to make the format extensible are:\n\n- Apache Arrow uses a self-describing schema with named columns, so it is straightforward to write code that is resilient in the face of things like additional columns being added.\n"
  },
  {
    "path": "docs/README.md",
    "content": "Design documentation for POD5\n============================\n\nThe POD5 file format has been specifically designed to be suitable for Nanopore read data, we had some specific design goals:\n\nDesign Goals\n------------\n\nThe primary purpose of this file format is store reads produced by Oxford Nanopore sequencing, and in particular the signal data from those reads (which can then be basecalled or processed in other ways).\n\nThis file format has the following design goals:\n\n- Good write performance for MinKNOW\n- Recoverable if the writing process crashes\n- Good read performance for downstream tools, including basecall model generation\n- Efficient use of space\n- Straightforward to implement and maintain\n- Extensibility\n\nNote that trade-offs have been made between these goals, but we have mostly aimed to make those run-time decisions.\n\nWe have also chosen not to optimise for editing existing files.\n\nMore detailed information around general format goals can be found in [DESIGN](./DESIGN.md), more detailed format specification is available in [SPECIFICATION](./SPECIFICATION.md).\n"
  },
  {
    "path": "docs/SPECIFICATION.md",
    "content": "POD5 Format Specification\n=========================\n\n## Overview\n\nThe file format is, at its core, a collection of Apache Arrow tables, stored in the Apache Feather 2\n(also know as Apache Arrow IPC File) format, and bundled into a container format. The container file\nhas the extension `.pod5`.\n\n### Table Schemas\n\nPOD5 files are a custom wrapper format around arrow that contain several [arrow\ntables](https://arrow.apache.org/docs/python/data.html#tables).\n\nAll the tables should have the following `custom_metadata` fields set on them:\n\n| Name                    | Example Value                        | Notes                                                                                                                                       |\n|-------------------------|--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|\n| MINKNOW:pod5_version    | 1.0.0                                | The version of this specification that the schema was based on.                                                                             |\n| MINKNOW:software        | MinNOW Core 5.2.3                    | A free-form description of the software that wrote the file, intended to  help pin down the source of files that violate the specification. |\n| MINKNOW:file_identifier | cbf91180-0684-4a39-bf56-41eaf437de9e | Must be identical across all tables. Allows checking that the files correspond to each other.                                               |\n\n### Extension Types\n\nSeveral fields in the table schemas use [custom arrow\ntypes](https://arrow.apache.org/docs/python/data.html#custom-schema-and-field-metadata).\n\n#### minknow.uuid\n\nThe schemas make extensive use of UUIDs to identify reads. This is stored using an extension type,\nwith the following properties:\n\n    Name: \"minknow.uuid\"\n    Physical storage: FixedBinary(16)\n\n#### minknow.vbz\n\nStorage for VBZ-encoded data:\n\n    Name: \"minknow.vbz\"\n    Physical storage: LargeBinary\n\n### Tables\n\nThe Reads, Signal and Run Info tables must all be present in a POD5 file. Note that some very early\nPOD5 files produced by pre-0.1 versions of the pod5 library did not include a Run Info table,\ninstead including that information in the Reads table.\n\n#### Reads Table\n\nThe Reads table contains a single row per read, and describes the metadata for each read. The\n`signal` column of the read links to the Signal table, and allows a reads signal to be retrieved.\nThe `run_info` column links to the the Run Info table, providing more context for the read and\navoiding duplicating data that is common to many or all reads in the file.\n\nSome fields of the Reads table are\n[dictionaries](https://arrow.apache.org/docs/python/data.html#dictionary-arrays): the contents of\nthe table are stored in a lookup written prior to each batch of read rows and the read row itself\nthen contains an integer index. This allows space savings on fields that would otherwise be\nrepeated. Only simple types are stored in dictionaries as third party tools have limited support for\ndictionaries of structs.\n\n[tables/reads.toml] contains specific information about fields in the reads table.\n\n#### Signal Table\n\nThe signal table contains the (optionally compressed) signal data where one row contains sequence of\nsample data, and some information about the sample data origin.\n\n[tables/signal.toml] contains specific information about fields in the signal table.\n\n#### Run Info Table\n\nThe run info table contains a single row per MinKNOW run that any read in the file came from.\n\nSeveral fields of the Reads table are\n[dictionaries](https://arrow.apache.org/docs/python/data.html#dictionary-arrays), the contents of\nthe table are stored in a lookup written prior to each batch of read rows, the read row itself then\ncontains an integer index. This allows space savings on fields that would otherwise be repeated.\n\n[tables/run_info.toml] contains specific information about fields in the reads table.\n\n### Combined file Layout\n\n#### Layout\n\n```\n<signature \"\\213POD\\r\\n\\032\\n\">\n<section marker: 16 bytes>\n<embedded file 1 (padded to 8-byte boundary)><section marker: 16 bytes>\n...\n<embedded file N (padded to 8-byte boundary)><section marker: 16 bytes>\n<footer magic: \"FOOTER\\000\\000\">\n<footer (padded to 8-byte boundary)>\n<footer length: 8 bytes little-endian signed integer>\n<section marker: 16 bytes>\n<signature \"\\213POD\\r\\n\\032\\n\">\n```\n\nAll padding bytes should be zero. They ensure memory mapped files have the alignment that Arrow\nexpects.\n\n#### Signature\n\nThe first and last eight bytes of the file are both a fixed set of values:\n\n```\n| Decimal          | 139  | 80   | 79   | 68   | 13   | 10   | 26   | 10   |\n| Hexadecimal      | 0x8B | 0x50 | 0x4F | 0x44 | 0x0D | 0x0A | 0x1A | 0x0A |\n| ASCII C Notation | \\213 | P    | O    | D    | \\r   | \\n   | \\032 | \\n   |\n```\n\nThe format of the signature is based on the PNG file signature, and inherits several useful features\nfrom it for detecting file corruption:\n\n- The first byte is non-ASCII to reduce the probability it is interpreted as a text file.\n- The first byte has the high bit set to catch file transfers that clear the top bit.\n- The \\r\\n (CRLF) sequence and the final \\n (LF) byte check that nothing has attempted to\n  standardise line endings in the file.\n- The second-last byte (\\032) is the CTRL-Z sequence, which stops file display under MS-DOS.\n\n##### Rationale\n\nA unique, fixed signature for the file type allows quickly identifying that the file is in the\nexpected format, and provides an easy way for tools like the UNIX `file` command to determine the\nfile type.\n\nPlacing it at the end allows quickly checking whether the file is complete.\n\n\n#### Section marker\n\nThe section marker is a 16-byte UUID, generated randomly for each file. All the section markers in a\ngiven file must be identical.\n\n##### Rationale\n\nThis aids in recovery of partially-written files (that are missing a footer) - while most of the\nembedded Arrow IPC files can be scanned easily, it may not be obvious where the footer ends. A given\nrandomly-generated 16-byte value is highly unlikely to occur in actual data, and can be scanned for\nto find the end of the embedded file for certain. The first section marker is just so that recovery\ntools know what to look for.\n\n#### Footer magic\n\nThis is the ASCII string \"FOOTER\" padded to 8 bytes with zeroes. It helps find a partially-written\nfooter when recovering files.\n\n#### Footer\n\nThe footer is an encoded [FlatBuffer](https://google.github.io/flatbuffers/) table, using the schema\nbelow.\n\n```fbs\nnamespace Minknow.ReadsFormat;\n\nenum ContentType:short {\n    // The Reads table (an Arrow table)\n    ReadsTable,\n    // The Signal table (an Arrow table)\n    SignalTable,\n    // An index for looking up data in the ReadsTable by read_id\n    ReadIdIndex,\n    // An index based on other columns and/or tables (it will need to be opened to find out what it indexes)\n    OtherIndex,\n}\n\nenum Format:short {\n    // The Apache Feather V2 format, also known as the Apache Arrow IPC File format.\n    FeatherV2,\n}\n\n// Describes an embedded file.\ntable EmbeddedFile {\n    // The start of the embedded file\n    offset: int64;\n    // The length of the embedded file (excluding any padding)\n    length: int64;\n    // The format of the file\n    format: Format;\n    // What contents should be expected in the file\n    content_type: ContentType;\n}\n\ntable Footer {\n    // Must match the \"MINKNOW:file_identifier\" custom metadata entry in the schemas of the bundled tables.\n    file_identifier: string;\n    // A free-form description of the software that wrote the file, intended to help pin down the source of files that violate the specification.\n    software: string;\n    // The version of this specification that the table schemas are based on (1.0.0).\n    pod5_version: string;\n    // The Apache Arrow tables stored in the file.\n    contents: [ EmbeddedFile ];\n}\n```\n\n##### Rationale\n\nFlatBuffers are used because the Arrow IPC file format already uses them for metadata, and they can\nbe read from a memory mapped file or read buffer without further copying. They are also easily (and\ncompatibly) extensible with more fields.\n\nA footer is used instead of a header so the file can be written incrementally: the first table can\nbe written directly to the file before it is known how long it will be or even how many tables there\nwill be.\n\n#### Footer length\n\nThis is a little-endian 8-byte signed integer giving the length of the footer buffer, including\npadding.\n\n##### Rationale\n\nThis allows readers to find the start of the footer by starting at the end of the file and reading\nbackwards.\n"
  },
  {
    "path": "docs/tables/reads.toml",
    "content": "[fields.read_id]\ntype = \"minknow.uuid\"\ndescription = \"Globally-unique identifier for the read, can be converted to a string form (using standard routines in other libraries) which matches how reads are identified elsewhere.\"\n\n[fields.signal]\ntype = \"list(uint64)\"\ndescription = \"A list of zero-indexed row numbers in the Signal table. This must be all the rows in the Signal table that have a matching read_id, in order. It functions as an index for the Signal table.\"\n\n[fields.channel]\ntype = \"uint16\"\ndescription = \"1-indexed channel\"\n\n[fields.well]\ntype = \"uint8\"\ndescription = \"1-indexed well (typically 1, 2, 3 or 4)\"\n\n[fields.pore_type]\ntype = \"dictionary(string)\"\ndescription = \"Name of the pore type present in the well\"\n\n[fields.calibration_offset]\ntype = \"float\"\ndescription = \"Calibration offset used to scale raw ADC data into pA readings.\"\n\n[fields.calibration_scale]\ntype = \"float\"\ndescription = \"Calibration scale factor used to scale raw ADC data into pA readings.\"\n\n[fields.read_number]\ntype = \"uint32\"\ndescription = \"The read number on channel. This is increasing but typically not necessarily consecutive.\"\n\n[fields.start]\ntype = \"uint64\"\ndescription = \"How many samples were taken on this channel before the read started (since the data acquisition period began). This can be combined with the sample rate to get a time in seconds for the start of the read relative to the start of data acquisition.\"\n\n[fields.median_before]\ntype = \"float\"\ndescription = \"The level of current in the well before this read (typically the open pore level of the well). If the level is not known (eg: due to a mux change), this should be nulled out.\"\n\n# Deprecated: will be removed in 0.4.0\n[fields.tracked_scaling_scale]\ntype = \"float\"\ndescription = \"Scale for tracked read scaling values (based on previous reads shift)\"\n\n# Deprecated: will be removed in 0.4.0\n[fields.tracked_scaling_shift]\ntype = \"float\"\ndescription = \"Shift for tracked read scaling values (based on previous reads shift)\"\n\n# Deprecated: will be removed in 0.4.0\n[fields.predicted_scaling_scale]\ntype = \"float\"\ndescription = \"Scale for predicted read scaling values (based on this read's raw signal)\"\n\n# Deprecated: will be removed in 0.4.0\n[fields.predicted_scaling_shift]\ntype = \"float\"\ndescription = \"Shift for predicted read scaling values (based on this read's raw signal)\"\n\n# Deprecated: will be removed in 0.4.0\n[fields.num_reads_since_mux_change]\ntype = \"uint32\"\ndescription = \"Number of selected reads since the last mux change on this reads channel\"\n\n# Deprecated: will be removed in 0.4.0\n[fields.time_since_mux_change]\ntype = \"float\"\ndescription = \"Time in seconds since the last mux change on this reads channel\"\n\n[fields.num_minknow_events]\ntype = \"uint64\"\ndescription = \"Number of minknow events that the read contains\"\n\n[fields.end_reason]\ntype = \"dictionary(string)\"\ndescription = \"The end reason, currently one of: unknown, mux_change, unblock_mux_change, data_service_unblock_mux_change, signal_positive, signal_negative, api_request, device_data_error, analysis_config_change or paused.\"\n\n[fields.end_reason_forced]\ntype = \"bool\"\ndescription = \"True if this read was ended 'forcibly' (eg: mux_change, unblock), false if it was a data-driven read break (signal_positive, signal_negative). This allows simple categorisation even in the presence of new reasons that reading code is unaware of.\"\n\n[fields.run_info]\ntype = \"dictionary(utf8)\"\ndescription = \"The run (acquisition) this read came from. Must match the acquisition_id field of exactly one entry in the run_info table.\"\n\n[fields.num_samples]\ntype = \"uint64\"\ndescription = \"The full length of the signal for this read in samples (equal to the sum of all 'samples' fields of signal chunks)\"\n\n[fields.open_pore_level]\ntype = \"float\"\ndescription = \"The open pore level for the read. A value value in pA showing the open pore level of the well prior to the read starting. If the information is not available (feature not enabled in MinKNOW, or sequencing run on an old version) this value will be NaN.\"\n"
  },
  {
    "path": "docs/tables/run_info.toml",
    "content": "[fields.acquisition_id]\ntype = \"utf8\"\ndescription = \"A unique identifier for the run (acquisition). This is the same identifier that MinKNOW uses to identify an acquisition within a protocol.\"\n\n[fields.acquisition_start_time]\ntype = \"timestamp(milliseconds)\"\ndescription = \"This is the clock time for sample 0, and can be used together with sample_rate and the :start read field to calculate a clock time for when a given read was acquired. The timezone should be set. MinKNOW will set this to the local timezone on file creation. When merging files that have different timezones, merging code will have to pick a timezone (possibly defaulting to 'UTC').\"\n\n[fields.adc_max]\ntype = \"int16\"\ndescription = \"The maximum ADC value that might be encountered. This is a hardware constraint.\"\n\n[fields.adc_min]\ntype = \"int16\"\ndescription = \"The minimum ADC value that might be encountered. This is a hardware constraint. adc_max - adc_min + 1 is the digitisation.\"\n\n[fields.context_tags]\ntype = \"map(utf8, utf8)\"\ndescription = \"The context tags for the run. For compatibility with fast5. Readers must not make any assumptions about the contents of this field.\"\n\n[fields.experiment_name]\ntype = \"utf8\"\ndescription = \"A user-supplied name for the experiment being run.\"\n\n[fields.flow_cell_id]\ntype = \"utf8\"\ndescription = \"Uniquely identifies the flow cell the data was captured on. This is written on the flow cell case.\"\n\n[fields.flow_cell_product_code]\ntype = \"utf8\"\ndescription = \"Identifies the type of flow cell the data was captured on.\"\n\n[fields.protocol_name]\ntype = \"utf8\"\ndescription = \"The name of the protocol that was run.\"\n\n[fields.protocol_run_id]\ntype = \"utf8\"\ndescription = \"A unique identifier for the protocol run that produced this data.\"\n\n[fields.protocol_start_time]\ntype = \"timestamp(milliseconds)\"\ndescription = \"): When the protocol that the acquisition was part of started. The same considerations apply as for acquisition_start_time.\"\n\n[fields.sample_id]\ntype = \"utf8\"\ndescription = \"A user-supplied name for the sample being analysed.\"\n\n[fields.sample_rate]\ntype = \"uint16\"\ndescription = \"The number of samples acquired each second on each channel. This can be used to convert numbers of samples into time durations.\"\n\n[fields.sequencing_kit]\ntype = \"utf8\"\ndescription = \"The type of sequencing kit used to prepare the sample.\"\n\n[fields.sequencer_position]\ntype = \"utf8\"\ndescription = \"The sequencer position the data was collected on. For removable positions, like MinION Mk1Bs, this is unique (e.g. 'MN12345'), while for integrated positions it is not (e.g. 'X1' on a GridION).\"\n\n[fields.sequencer_position_type]\ntype = \"utf8\"\ndescription = \"The type of sequencing hardware the data was collected on. For example: 'MinION Mk1B' or 'GridION' or 'PromethION'.\"\n\n[fields.software]\ntype = \"utf8\"\ndescription = \"A description of the software that acquired the data. For example: 'MinKNOW 21.05.12 (Bream 5.1.6, Configurations 16.2.1, Core 5.1.9, Guppy 4.2.3)'.\"\n\n[fields.system_name]\ntype = \"utf8\"\ndescription = \"The name of the system the data was collected on. This might be a sequencer serial (eg: 'GXB1234') or a host name (e.g. 'Lab PC').\"\n\n[fields.system_type]\ntype = \"utf8\"\ndescription = \"The type of system the data was collected on. For example, 'GridION Mk1' or 'PromethION P48'. If the system is not a Nanopore sequencer with built-in compute, this will be a description of the operating system (e.g. 'Ubuntu 20.04').\"\n\n[fields.tracking_id]\ntype = \"map(utf8, utf8)\"\ndescription = \"The tracking id for the run. For compatibility with fast5. Readers must not make any assumptions about the contents of this field.\"\n"
  },
  {
    "path": "docs/tables/signal.toml",
    "content": "[fields.read_id]\ntype = \"minknow.uuid\"\ndescription = \"Globally-unique identifier for the read the data came from. This aids recovery and consistency checking.\"\n\n[fields.signal]\ntype = [ \"large_list(int16)\", \"minknow.vbz\" ]\ndescription = \"The actual signal. The encoding of the data must the same for all reads in the file, and is determined by the choice of logical type. LargeList(Int16) is the uncompressed storage option. Readers that do not recognise the logical type of this column will be unable to decode the signal data.\"\n\n[fields.samples]\ntype = \"uint32\"\ndescription = \"The number of samples stored in this row. Allows skipping over compressed chunks easily, also necessary for decoding StreamVByte-encoded data.\"\n"
  },
  {
    "path": "fuzz/.gitattributes",
    "content": "# Store all corpora in LFS\n*.zip filter=lfs diff=lfs merge=lfs -text\n"
  },
  {
    "path": "fuzz/CMakeLists.txt",
    "content": "set(FUZZER_RUN_TIME 0 CACHE STRING \"How long to run each fuzzer for. 0 indicates no limit\")\n\n# The fuzzer only uses nproc/2 workers, so limit the number of jobs to that.\ninclude(ProcessorCount)\nProcessorCount(NUM_FUZZER_JOBS)\nmath(EXPR NUM_FUZZER_JOBS \"${NUM_FUZZER_JOBS} / 2\")\n\n# Make sure that all issues get picked up\nset(FUZZ_SANITIZER_OPTIONS\n    # Note that we have to disable |detect_leaks| since arrow will allocate persistent state.\n    \"ASAN_OPTIONS=string_append::detect_stack_use_after_return=1:check_initialization_order=1:detect_leaks=0\"\n    \"UBSAN_OPTIONS=string_append::print_stacktrace=1:halt_on_error=1\"\n)\n\nfunction(make_fuzzer TYPE)\n    # Extract the corpus.\n    set(CORPUS ${CMAKE_CURRENT_SOURCE_DIR}/corpus_${TYPE})\n    file(ARCHIVE_EXTRACT INPUT ${CORPUS}.zip DESTINATION ${CORPUS})\n\n    if (ENABLE_FUZZERS)\n        # Setup the target\n        set(NAME fuzzer_${TYPE})\n        add_executable(${NAME} fuzz_${TYPE}.cpp)\n        target_link_libraries(${NAME} PUBLIC pod5_format)\n        target_link_options(${NAME} PUBLIC -fsanitize=fuzzer)\n        target_compile_definitions(${NAME} PRIVATE BUILD_SHARED_LIB=$<BOOL:${BUILD_SHARED_LIB}>)\n\n        # Add a test for it\n        add_test(\n            NAME ${NAME}\n            COMMAND ${NAME} -jobs=${NUM_FUZZER_JOBS} -max_total_time=${FUZZER_RUN_TIME} -timeout=100 ${CORPUS}\n            # Run in the current working directory so that failing cases are dumped there.\n            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n        )\n        set_tests_properties(${NAME} PROPERTIES ENVIRONMENT_MODIFICATION \"${FUZZ_SANITIZER_OPTIONS}\")\n    endif()\n\n    # Add a runner for the fuzzer in non-fuzzing builds.\n    set(NAME fuzz_runner_${TYPE})\n    add_executable(${NAME}\n        fuzz_${TYPE}.cpp\n        runner.cpp\n    )\n    target_link_libraries(${NAME} PRIVATE pod5_format)\n    # Enable use of std::filesystem in the runner.\n    target_compile_features(${NAME} PRIVATE cxx_std_17)\n    # Make sure that assert() functions correctly even in Release builds.\n    target_compile_options(${NAME} PRIVATE -UNDEBUG)\n    target_compile_definitions(${NAME} PRIVATE BUILD_SHARED_LIB=$<BOOL:${BUILD_SHARED_LIB}>)\n    add_test(\n        NAME ${NAME}\n        COMMAND ${NAME} ${CORPUS}\n    )\n    set_tests_properties(${NAME} PROPERTIES ENVIRONMENT_MODIFICATION \"${FUZZ_SANITIZER_OPTIONS}\")\nendfunction()\n\nmake_fuzzer(compress)\nmake_fuzzer(file)\n"
  },
  {
    "path": "fuzz/fuzz_compress.cpp",
    "content": "#include <pod5_format/c_api.h>\n\n#include <cassert>\n#include <cstdio>\n#include <cstdlib>\n#include <cstring>\n#include <vector>\n\n#ifndef _WIN32\n#include <unistd.h>\n#else\n#include <process.h>\n\nstatic int setenv(char const * name, char const * value, int) { return _putenv_s(name, value); }\n#endif\n\n// No access to arrow in shared lib builds.\n#if !BUILD_SHARED_LIB\n#include <arrow/memory_pool.h>\n#endif\n\n#ifdef NDEBUG\n#error \"asserts aren't enabled\"\n#endif\n\nnamespace {\nvoid CheckPod5(pod5_error_t err, char const * msg)\n{\n    if (err != POD5_OK) {\n        printf(\"Assertion failed: %s - %i - %s\\n\", msg, err, pod5_get_error_string());\n        assert(false);\n    }\n    if (err != pod5_get_error_no()) {\n        printf(\"POD5 inconsistency: %s - %i != %i\\n\", msg, err, pod5_get_error_no());\n        assert(false);\n    }\n}\n\n#define CHECK_POD5(x) CheckPod5(x, #x)\n}  // namespace\n\nextern \"C\" int LLVMFuzzerInitialize(int * argc, char *** argv)\n{\n    // Make sure arrow uses the system allocator\n    setenv(\"ARROW_DEFAULT_MEMORY_POOL\", \"system\", 1);\n#if !BUILD_SHARED_LIB\n    assert(arrow::system_memory_pool()->backend_name() == \"system\");\n#endif\n\n    // Init pod5\n    CHECK_POD5(pod5_init());\n    return 0;\n}\n\nextern \"C\" int LLVMFuzzerTestOneInput(uint8_t const * data, size_t size)\n{\n    // POD5 requires non-empty input\n    if (size < sizeof(int16_t)) {\n        return 0;\n    }\n\n    // Copy to a new buffer of the \"right\" type so that we get bounds checking even if the length wasn't even\n    std::vector<int16_t> input(size / sizeof(int16_t));\n    std::memcpy(input.data(), data, input.size() * sizeof(int16_t));\n\n    // Compress it\n    size_t const max_compressed_size = pod5_vbz_compressed_signal_max_size(input.size());\n    std::vector<char> compressed_data(max_compressed_size);\n    size_t compressed_size = compressed_data.size();\n    CHECK_POD5(pod5_vbz_compress_signal(\n        input.data(), input.size(), compressed_data.data(), &compressed_size));\n    assert(compressed_size <= max_compressed_size);\n\n    // Update size for bounds checking when decompressing\n    compressed_data =\n        std::vector<char>(compressed_data.begin(), compressed_data.begin() + compressed_size);\n\n    // Decompress it\n    std::vector<int16_t> output(input.size());\n    CHECK_POD5(pod5_vbz_decompress_signal(\n        compressed_data.data(), compressed_data.size(), output.size(), output.data()));\n\n    // Check it decompressed correctly\n    assert(input == output);\n\n    // See how it handles random input\n    std::vector<int16_t> temp(pod5_vbz_compressed_signal_max_size(size));\n    pod5_vbz_decompress_signal(\n        reinterpret_cast<char const *>(data), size, temp.size(), temp.data());\n\n    return 0;\n}\n"
  },
  {
    "path": "fuzz/fuzz_file.cpp",
    "content": "#include <pod5_format/c_api.h>\n\n#include <algorithm>\n#include <array>\n#include <cassert>\n#include <cstdio>\n#include <cstdlib>\n#include <cstring>\n#include <memory>\n#include <random>\n#include <string>\n#include <vector>\n\n#ifndef _WIN32\n#include <unistd.h>\n#else\n#include <process.h>\n\nstatic int setenv(char const * name, char const * value, int) { return _putenv_s(name, value); }\n#endif\n\n// No access to arrow in shared lib builds.\n#if !BUILD_SHARED_LIB\n#include <arrow/memory_pool.h>\n#endif\n\n#ifdef NDEBUG\n#error \"asserts aren't enabled\"\n#endif\n\nnamespace {\n// Global state\nstd::string s_file_name;\n\n// Write out data to the temp file.\nchar const * write_data(void const * data, size_t size)\n{\n    FILE * f = fopen(s_file_name.c_str(), \"w\");\n    assert(f != nullptr);\n    fwrite(data, 1, size, f);\n    fclose(f);\n    return s_file_name.c_str();\n}\n\n// Check the result of a POD5 call.\nvoid check_pod5_ok(pod5_error_t err, char const * msg)\n{\n    if (err != POD5_OK) {\n        printf(\"Assertion failed: %s - %i - %s\\n\", msg, err, pod5_get_error_string());\n        assert(false);\n    }\n}\n\n// Check that the return value of a function always matches pod5_get_error_no().\nvoid check_pod5_consistency(pod5_error_t err, char const * msg)\n{\n    if (err != pod5_get_error_no()) {\n        printf(\"POD5 inconsistency: %s - %i != %i\\n\", msg, err, pod5_get_error_no());\n        assert(false);\n    }\n}\n\n#define CHECK_POD5_SUCCESS(func)             \\\n    do {                                     \\\n        auto _res = func;                    \\\n        check_pod5_ok(_res, #func);          \\\n        check_pod5_consistency(_res, #func); \\\n    } while (false)\n\n#define CHECK_POD5_MAY_FAIL(func) check_pod5_consistency(func, #func)\n\n// Helper to stop the optimiser from removing results.\ntemplate <typename T>\nvoid keep_result(T && t)\n{\n    auto volatile v = t;\n    (void)v;\n}\n\n// Make sure that a string is NUL-terminated.\nvoid validate_string(char const * ptr)\n{\n    std::string str = ptr;\n    keep_result(str);\n}\n\n// File handle wrapper\nstruct POD5FileCloser {\n    void operator()(Pod5FileReader_t * file)\n    {\n        if (file != nullptr) {\n            CHECK_POD5_SUCCESS(pod5_close_and_free_reader(file));\n        }\n    }\n};\n\nusing POD5File = std::unique_ptr<Pod5FileReader_t, POD5FileCloser>;\n}  // namespace\n\nextern \"C\" int LLVMFuzzerInitialize(int * argc, char *** argv)\n{\n    // Make sure arrow uses the system allocator\n    setenv(\"ARROW_DEFAULT_MEMORY_POOL\", \"system\", 1);\n#if !BUILD_SHARED_LIB\n    assert(arrow::system_memory_pool()->backend_name() == \"system\");\n#endif\n\n    // Init POD5\n    CHECK_POD5_SUCCESS(pod5_init());\n\n    // Setup state shared for all runs\n    s_file_name = \"./fuzz_tmp_\" + std::to_string(getpid());\n    return 0;\n}\n\nextern \"C\" int LLVMFuzzerTestOneInput(uint8_t const * data, size_t size)\n{\n    // Write the input to a file\n    char const * file_path = write_data(data, size);\n\n    // Try and open it\n    POD5File file(pod5_open_file(file_path));\n    if (file == nullptr) {\n        return 0;\n    }\n\n    // Check that we can query info about the file.\n    FileInfo_t file_info{};\n    CHECK_POD5_SUCCESS(pod5_get_file_info(file.get(), &file_info));\n\n    // If we need any more randomness, use the file's ID as a seed.\n    std::seed_seq seed(std::begin(file_info.file_identifier), std::end(file_info.file_identifier));\n    std::mt19937_64 rng(seed);\n\n    // See what IDs there are\n    std::size_t batch_count = 0;\n    CHECK_POD5_SUCCESS(pod5_get_read_batch_count(&batch_count, file.get()));\n\n    std::size_t total_read_count = 0;\n    for (std::size_t batch_index = 0; batch_index < batch_count; ++batch_index) {\n        Pod5ReadRecordBatch_t * batch = nullptr;\n        CHECK_POD5_MAY_FAIL(pod5_get_read_batch(&batch, file.get(), batch_index));\n        if (batch == nullptr) {\n            continue;\n        }\n\n        std::size_t batch_row_count = 0;\n        CHECK_POD5_SUCCESS(pod5_get_read_batch_row_count(&batch_row_count, batch));\n        total_read_count += batch_row_count;\n\n        for (std::size_t row = 0; row < batch_row_count; ++row) {\n            uint16_t read_table_version = 0;\n            ReadBatchRowInfo_t read_data;\n            CHECK_POD5_SUCCESS(pod5_get_read_batch_row_info_data(\n                batch, row, READ_BATCH_ROW_INFO_VERSION, &read_data, &read_table_version));\n\n            // Check read formatter.\n            std::array<char, 37> formatted_read_id;\n            CHECK_POD5_SUCCESS(pod5_format_read_id(read_data.read_id, formatted_read_id.data()));\n            validate_string(formatted_read_id.data());\n\n            // Check signal indices.\n            assert(read_data.signal_row_count >= 0);\n            if (read_data.signal_row_count > 0 && read_data.signal_row_count < 1'000'000) {\n                std::vector<uint64_t> indices(read_data.signal_row_count);\n                CHECK_POD5_SUCCESS(\n                    pod5_get_signal_row_indices(batch, row, indices.size(), indices.data()));\n            }\n\n            // Check signal extraction.\n            std::size_t sample_count = 0;\n            CHECK_POD5_MAY_FAIL(\n                pod5_get_read_complete_sample_count(file.get(), batch, row, &sample_count));\n            if (sample_count < 1'000'000) {\n                std::vector<int16_t> samples(sample_count);\n                CHECK_POD5_MAY_FAIL(pod5_get_read_complete_signal(\n                    file.get(), batch, row, samples.size(), samples.data()));\n            }\n\n            // Check calibration data.\n            CalibrationExtraData_t calib_data;\n            CHECK_POD5_MAY_FAIL(pod5_get_calibration_extra_info(batch, row, &calib_data));\n\n            // Check run info.\n            RunInfoDictData_t * run_info = nullptr;\n            CHECK_POD5_MAY_FAIL(pod5_get_run_info(batch, read_data.run_info, &run_info));\n            // We'll do a proper check of the run info later.\n            if (run_info != nullptr) {\n                CHECK_POD5_SUCCESS(pod5_free_run_info(run_info));\n            }\n        }\n\n        // Check run info.\n        run_info_index_t run_info_count = 0;\n        CHECK_POD5_MAY_FAIL(pod5_get_file_run_info_count(file.get(), &run_info_count));\n        for (run_info_index_t run_info_idx = 0; run_info_idx < run_info_count; run_info_idx++) {\n            RunInfoDictData_t * run_info_data = nullptr;\n            CHECK_POD5_SUCCESS(pod5_get_file_run_info(file.get(), run_info_idx, &run_info_data));\n            assert(run_info_data != nullptr);\n\n            validate_string(run_info_data->acquisition_id);\n            validate_string(run_info_data->experiment_name);\n            validate_string(run_info_data->flow_cell_id);\n            validate_string(run_info_data->flow_cell_product_code);\n            validate_string(run_info_data->protocol_name);\n            validate_string(run_info_data->protocol_run_id);\n            validate_string(run_info_data->sample_id);\n            validate_string(run_info_data->sequencing_kit);\n            validate_string(run_info_data->sequencer_position);\n            validate_string(run_info_data->sequencer_position_type);\n            validate_string(run_info_data->software);\n            validate_string(run_info_data->system_name);\n            validate_string(run_info_data->system_type);\n            for (std::size_t i = 0; i < run_info_data->context_tags.size; i++) {\n                validate_string(run_info_data->context_tags.keys[i]);\n                validate_string(run_info_data->context_tags.values[i]);\n            }\n            for (std::size_t i = 0; i < run_info_data->tracking_id.size; i++) {\n                validate_string(run_info_data->tracking_id.keys[i]);\n                validate_string(run_info_data->tracking_id.values[i]);\n            }\n\n            CHECK_POD5_SUCCESS(pod5_free_run_info(run_info_data));\n        }\n\n        // Cleanup.\n        CHECK_POD5_SUCCESS(pod5_free_read_batch(batch));\n    }\n\n    {\n        // Check total read count matches.\n        std::size_t read_count = 0;\n        CHECK_POD5_MAY_FAIL(pod5_get_read_count(file.get(), &read_count));\n        if (pod5_get_error_no() == POD5_OK) {\n            assert(read_count == total_read_count);\n        } else {\n            read_count = 0;\n        }\n\n        if (read_count > 0) {\n            // Query all the reads IDs.\n            std::vector<uint8_t> read_ids(read_count * sizeof(read_id_t));\n            CHECK_POD5_SUCCESS(pod5_get_read_ids(\n                file.get(), read_count, reinterpret_cast<read_id_t *>(read_ids.data())));\n\n            // Randomise the order of the read IDs and then try and plan a path through them.\n            std::shuffle(read_ids.begin(), read_ids.end(), rng);\n            std::vector<std::uint32_t> batch_counts(read_count);\n            std::vector<std::uint32_t> batch_rows(read_count);\n            std::size_t find_success_count = 0;\n            CHECK_POD5_MAY_FAIL(pod5_plan_traversal(\n                file.get(),\n                reinterpret_cast<uint8_t const *>(read_ids.data()),\n                read_count,\n                batch_counts.data(),\n                batch_rows.data(),\n                &find_success_count));\n            assert(find_success_count <= read_count);\n        }\n    }\n\n    // Check embedded files.\n    {\n        for (auto * pod5_get_embedded_file : {\n                 pod5_get_file_read_table_location,\n                 pod5_get_file_signal_table_location,\n                 pod5_get_file_run_info_table_location,\n             })\n        {\n            EmbeddedFileData_t file_data{};\n            CHECK_POD5_SUCCESS(pod5_get_embedded_file(file.get(), &file_data));\n            validate_string(file_data.file_name);\n            assert(file_data.offset <= size);\n            assert(file_data.length <= size - file_data.offset);\n        }\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "fuzz/runner.cpp",
    "content": "#include <cassert>\n#include <cstdlib>\n#include <filesystem>\n#include <fstream>\n#include <iostream>\n#include <vector>\n\n#ifdef NDEBUG\n#error \"asserts aren't enabled\"\n#endif\n\nextern \"C\" {\nint LLVMFuzzerInitialize(int * argc, char *** argv);\nint LLVMFuzzerTestOneInput(uint8_t const * data, size_t size);\n}\n\nnamespace {\n\nvoid run_one(bool verbose, std::filesystem::path const & path)\n{\n    if (verbose) {\n        std::cout << \"Running \" << path.string() << std::endl;\n    }\n\n    // Read it into a buffer.\n    std::vector<uint8_t> data(std::filesystem::file_size(path));\n    std::ifstream file(path, std::ios::in | std::ios::binary);\n    file.read(reinterpret_cast<char *>(data.data()), data.size());\n    assert(file);\n\n    // Run it.\n    LLVMFuzzerTestOneInput(data.data(), data.size());\n}\n\nvoid run_recursive(bool verbose, std::filesystem::path const & path)\n{\n    for (auto const & dirent : std::filesystem::directory_iterator(path)) {\n        if (std::filesystem::is_directory(dirent)) {\n            run_recursive(verbose, dirent.path());\n        } else if (std::filesystem::is_regular_file(dirent)) {\n            run_one(verbose, dirent.path());\n        }\n    }\n}\n\n}  // namespace\n\nint main(int argc, char ** argv)\n{\n    // Parse args.\n    char const * corpus = nullptr;\n    bool verbose = false;\n    if (argc == 3) {\n        verbose = true;\n        corpus = argv[2];\n    } else if (argc == 2) {\n        corpus = argv[1];\n    } else {\n        std::cerr << \"Usage: \" << argv[0] << \" [-v] <corpus>\" << std::endl;\n        return EXIT_FAILURE;\n    }\n\n    // Setup.\n    LLVMFuzzerInitialize(&argc, &argv);\n\n    // Run it.\n    std::filesystem::path corpus_path(corpus);\n    if (std::filesystem::is_directory(corpus_path)) {\n        std::cout << \"Running all corpus files\" << std::endl;\n        run_recursive(verbose, corpus_path);\n    } else if (std::filesystem::is_regular_file(corpus_path)) {\n        std::cout << \"Running single file\" << std::endl;\n        run_one(verbose, corpus_path);\n    } else {\n        std::cerr << \"Unknown file type: \" << corpus << std::endl;\n        return EXIT_FAILURE;\n    }\n\n    std::cout << \"Success!\" << std::endl;\n    return EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "pod5_make_version.py",
    "content": "\"\"\"\nWrite the POD5Version.cmake file by inspecting the _version.py file created by\nsetuptools_scm\n\"\"\"\n\nfrom pathlib import Path\nfrom _version import __version__, __version_tuple__\n\n\ndef create_pod5_version_cmake():\n    \"\"\"Use the _version.py output from setuptools_scm to define the pod5 version\"\"\"\n    with (Path(__file__).parent / \"cmake/POD5Version.cmake\").open(\"w\") as _fh:\n        vtup = __version_tuple__\n        _fh.writelines(\n            [\n                \"# Created by pod5_make_version.py \\n\",\n                f\"set(POD5_VERSION_MAJOR {vtup[0]} )\\n\",\n                f\"set(POD5_VERSION_MINOR {vtup[1]} )\\n\",\n                f\"set(POD5_VERSION_REV {vtup[2]} )\\n\",\n                f\"set(POD5_NUMERIC_VERSION {vtup[0]}.{vtup[1]}.{vtup[2]} )\\n\",\n                f\"set(POD5_FULL_VERSION {__version__} ) \\n\",\n            ]\n        )\n\n\ndef copy_version_py():\n    \"\"\"Copy the _version.py file into the lib_pod5 and pod5 project directories\"\"\"\n    vpy = Path(\"_version.py\")\n    assert vpy.exists()\n\n    lib_vpy = Path(__file__).parent / \"python/lib_pod5/src/lib_pod5/_version.py\"\n    api_vpy = Path(__file__).parent / \"python/pod5/src/pod5/_version.py\"\n\n    if lib_vpy.exists():\n        lib_vpy.unlink()\n\n    if api_vpy.exists():\n        api_vpy.unlink()\n\n    lib_vpy.write_bytes(vpy.read_bytes())\n    api_vpy.write_bytes(vpy.read_bytes())\n\n\nif __name__ == \"__main__\":\n    print(f\"Writing POD5Version.cmake with version: {__version__}\")\n    create_pod5_version_cmake()\n\n    print(\"Copying _version.py into lib_pod5 and pod5 source directories\")\n    copy_version_py()\n"
  },
  {
    "path": "pyproject.toml",
    "content": "# This project file is used to generate the version information written to _version.py\n# To create this file, simply pip install the root pod-file-format directory\n\n[build-system]\nrequires = [\"setuptools >= 61.0\", \"wheel\", \"setuptools_scm[toml]>=6.2\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"pod5version\"\nrequires-python = \">= 3.9, < 4.0\"\ndynamic = [\"version\"]\ndescription=\"Oxford Nanopore Technologies Pod5 File Format VCS Versioning\"\n\n[tool.setuptools.packages.find]\n# Ignore all files\nexclude = [\"*\"]\n\n[tool.setuptools_scm]\nroot = \".\"\nwrite_to = \"_version.py\"\n"
  },
  {
    "path": "pytest.ini",
    "content": "[pytest]\nminversion = 6.0\n\naddopts =\n    # Report details on all non-passing tests\n    -ra\n    --verbose\n    --color=yes\n\n# Warnings are errors\nfilterwarnings =\n    error\n    ignore::DeprecationWarning\n\n# Find tests in both python projects\ntestpaths =\n    python/lib_pod5/\n    python/pod5/\n"
  },
  {
    "path": "python/.gitignore",
    "content": "__pycache__/\n*.egg-info/\n*.whl\n*.so\n.DS_Store\nlicenses/\n"
  },
  {
    "path": "python/lib_pod5/Makefile",
    "content": "SHELL = /bin/bash\nPYTHON ?= python3.7\n\nenvDir = venv\nenvPrompt ?= \"lib-pod5\"\n\n.PHONY: clean venv update install\n\n# Clean the python virtual environment\nclean:\n\trm -rf ${envDir}\n\n# Create a python virtual environment\nvenv:\n\t${PYTHON} -m venv ${envDir} --prompt=${envPrompt}\n\n# install the python project in the current venv\nupdate:\n\tsource ${envDir}/bin/activate \\\n\t&& pip install --upgrade pip \\\n\t&& pip install -e .[dev]\n\n# Completely re-install the python environment for development\ninstall: clean venv update\n\t@echo \"To activate your new environment:  source ${envDir}/bin/activate\"\n\n# Build the wheel\nwheel: update\n\tsource ${envDir}/bin/activate \\\n\t&& python -m build\n"
  },
  {
    "path": "python/lib_pod5/README.md",
    "content": "LIB_POD5 Package\n================\n\nPOD5 is a file format for storing nanopore dna data in an easily accessible way.\n\nWhat does this project contain\n------------------------------\n\nThis project contains the low-level core library (extension modules) for reading and\nwriting POD5 files. This project forms the basis of the pure-python `pod5` package which\nis probably the project you want.\n"
  },
  {
    "path": "python/lib_pod5/pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools >= 61.0\", \"wheel\", \"pybind11~=2.10.0\"]\nbuild-backend = \"setuptools.build_meta\"\n\n\n[project]\nname = \"lib_pod5\"\nauthors = [{name=\"Oxford Nanopore Technologies plc\", email=\"support@nanoporetech.com\"}]\nreadme=\"README.md\"\nrequires-python=\">= 3.9, < 4.0\"\ndescription=\"Python bindings for the POD5 file format\"\ndynamic = [\"version\"]\nkeywords = ['nanopore']\nclassifiers=[\n    'Environment :: Console',\n    'Intended Audience :: Developers',\n    'Intended Audience :: Science/Research',\n    'Natural Language :: English',\n    'Programming Language :: Python :: 3',\n    'Topic :: Scientific/Engineering :: Bio-Informatics',\n]\ndependencies = [\"numpy>=1.21.0\"]\nlicense=\"MPL-2.0\"\nlicense-files = [\"licenses/**/*\"]\n\n\n[project.optional-dependencies]\ndev = [\n    \"build\",\n    \"pytest ~= 7.3\",\n]\n\n[project.urls]\nHomepage = \"https://github.com/nanoporetech/pod5-file-format\"\nIssues = \"https://github.com/nanoporetech/pod5-file-format/issues\"\nDocumentation = \"https://pod5-file-format.readthedocs.io/en/latest/\"\n\n[tool.setuptools.dynamic]\nversion = {attr = \"lib_pod5._version.__version__\"}\n\n[tool.setuptools.packages.find]\nwhere = [\"src\"]\nexclude = [\"test\"]\n\n[tool.setuptools.package-data]\n\"lib_pod5\" = [\"*\"]\n\n[tool.black]\ntarget-version = [\"py38\"]\n"
  },
  {
    "path": "python/lib_pod5/setup.py",
    "content": "\"\"\"\nlib_pod5 setup.py\nProprietary and confidential information of Oxford Nanopore Technologies plc\nAll rights reserved; (c)2022: Oxford Nanopore Technologies plc\n\nThis script can either install a development version of pod5 to the current\nPython environment, or create a Python wheel.\n\nNote that this is *not* intended to be run from within the \"pod5\" folder of\nthe pod5_file_format repository, because the libraries are\nnot actually installed there. See INSTALL.md for further details.\n\n\"\"\"\n\nimport os\nimport sys\n\nimport setuptools\n\nextra_setup_args = {}\n\nif \"bdist_wheel\" in sys.argv:\n    # We need to convince distutils to put lib-pod5 in a platform-dependent\n    # location (as opposed to a \"universal\" one) or auditwheel will complain\n    # later. This is a hack to get it to do that.\n    # See https://github.com/pypa/auditwheel/pull/28#issuecomment-212082647\n    import platform\n    from distutils.command.install import install\n\n    from wheel.bdist_wheel import bdist_wheel\n\n    class BinaryInstall(install):\n        def __init__(self, dist):\n            super().__init__(dist)\n            # We should be able to set install_lib = self.install_platlib\n            # but that doesn't appear to work on OSX or Linux, so we have to do this.\n            if platform.system() != \"Windows\":\n                self.install_lib = \"\"\n            else:\n                self.install_lib = self.install_platlib\n\n    class BdistWheel(bdist_wheel):\n        def finalize_options(self):\n            bdist_wheel.finalize_options(self)\n            self.root_is_pure = False\n\n        def get_tag(self):\n            python, abi, plat = bdist_wheel.get_tag(self)\n            if \"FORCE_PYTHON_PLATFORM\" in os.environ:\n                plat = os.environ[\"FORCE_PYTHON_PLATFORM\"]\n            return python, abi, plat\n\n    extra_setup_args[\"cmdclass\"] = {\"install\": BinaryInstall, \"bdist_wheel\": BdistWheel}\n\n\nif __name__ == \"__main__\":\n    setuptools.setup(\n        has_ext_modules=lambda: True,\n        **extra_setup_args,\n    )\n"
  },
  {
    "path": "python/lib_pod5/src/lib_pod5/__init__.py",
    "content": "\"\"\"Imports everything from the pod5_format_pybind11 module and associated .pyi\"\"\"\n\nfrom ._version import __version__, __version_tuple__\nfrom .pod5_format_pybind import (\n    EmbeddedFileData,\n    FileWriter,\n    FileWriterOptions,\n    Pod5AsyncSignalLoader,\n    Pod5FileReader,\n    Pod5RepackerOutput,\n    Pod5SignalCacheBatch,\n    RecoverFileOptions,\n    RecoveredRowCounts,\n    Repacker,\n    SignalType,\n    compress_signal,\n    create_file,\n    recover_file,\n    decompress_signal,\n    format_read_id_to_str,\n    get_error_string,\n    load_read_id_iterable,\n    open_file,\n    update_file,\n    subset_pod5s_with_mapping,\n    vbz_compressed_signal_max_size,\n)\n\n__all__ = [\n    \"__version__\",\n    \"__version_tuple__\",\n    \"EmbeddedFileData\",\n    \"FileWriter\",\n    \"FileWriterOptions\",\n    \"Pod5AsyncSignalLoader\",\n    \"Pod5FileReader\",\n    \"Pod5RepackerOutput\",\n    \"Pod5SignalCacheBatch\",\n    \"RecoverFileOptions\",\n    \"RecoveredRowCounts\",\n    \"Repacker\",\n    \"SignalType\",\n    \"compress_signal\",\n    \"create_file\",\n    \"recover_file\",\n    \"decompress_signal\",\n    \"format_read_id_to_str\",\n    \"get_error_string\",\n    \"load_read_id_iterable\",\n    \"open_file\",\n    \"update_file\",\n    \"subset_pod5s_with_mapping\",\n    \"vbz_compressed_signal_max_size\",\n]\n"
  },
  {
    "path": "python/lib_pod5/src/lib_pod5/pod5_format_pybind.pyi",
    "content": "\"\"\"\nc++ bindings for pod5_format\n\"\"\"\n\n# pylint: skip-file\n\n# created with mypy.stubgen for code completion\n# > pip install mypy\n# > stubgen -m lib_pod5.pod5_format_pybind\n\nfrom typing import Any, Iterable, List, Optional, Tuple, Union\n\nimport numpy as np\nimport numpy.typing as npt\n\nclass CleanupError:\n    description: str\n    file_path: str\n    def __init__(self) -> None: ...\n\nclass EmbeddedFileData:\n    def __init__(self, *args, **kwargs) -> None: ...\n    @property\n    def length(self) -> int: ...\n    @property\n    def offset(self) -> int: ...\n    @property\n    def file_path(self) -> str: ...\n\nclass FileWriter:\n    def __init__(self, *args, **kwargs) -> None: ...\n    def add_end_reason(self, end_reason_enum: int) -> int: ...\n    def add_pore(self, pore_type: str) -> int: ...\n    def add_reads(\n        self,\n        count: int,\n        read_ids: npt.NDArray[np.uint8],\n        read_numbers: npt.NDArray[np.uint32],\n        start_samples: npt.NDArray[np.uint64],\n        channels: npt.NDArray[np.uint16],\n        wells: npt.NDArray[np.uint8],\n        pore_types: npt.NDArray[np.int16],\n        calibration_offsets: npt.NDArray[np.float32],\n        calibration_scales: npt.NDArray[np.float32],\n        median_befores: npt.NDArray[np.float32],\n        end_reasons: npt.NDArray[np.int16],\n        end_reason_forceds: npt.NDArray[np.bool_],\n        run_infos: npt.NDArray[np.int16],\n        num_minknow_events: npt.NDArray[np.uint64],\n        tracked_scaling_scales: npt.NDArray[np.float32],\n        tracked_scaling_shifts: npt.NDArray[np.float32],\n        predicted_scaling_scales: npt.NDArray[np.float32],\n        predicted_scaling_shifts: npt.NDArray[np.float32],\n        num_reads_since_mux_changes: npt.NDArray[np.uint32],\n        time_since_mux_changes: npt.NDArray[np.float32],\n        open_pore_levels: npt.NDArray[np.float32],\n        signals: List[npt.NDArray[np.int16]],\n    ) -> None: ...\n    def add_reads_pre_compressed(\n        self,\n        count: int,\n        read_ids: npt.NDArray[np.uint8],\n        read_numbers: npt.NDArray[np.uint32],\n        start_samples: npt.NDArray[np.uint64],\n        channels: npt.NDArray[np.uint16],\n        wells: npt.NDArray[np.uint8],\n        pore_types: npt.NDArray[np.int16],\n        calibration_offsets: npt.NDArray[np.float32],\n        calibration_scales: npt.NDArray[np.float32],\n        median_befores: npt.NDArray[np.float32],\n        end_reasons: npt.NDArray[np.int16],\n        end_reason_forceds: npt.NDArray[np.bool_],\n        run_infos: npt.NDArray[np.int16],\n        num_minknow_events: npt.NDArray[np.uint64],\n        tracked_scaling_scales: npt.NDArray[np.float32],\n        tracked_scaling_shifts: npt.NDArray[np.float32],\n        predicted_scaling_scales: npt.NDArray[np.float32],\n        predicted_scaling_shifts: npt.NDArray[np.float32],\n        num_reads_since_mux_changes: npt.NDArray[np.uint32],\n        time_since_mux_changes: npt.NDArray[np.float32],\n        open_pore_levels: npt.NDArray[np.float32],\n        signal_chunks: List[npt.NDArray[np.uint8]],\n        signal_chunk_lengths: npt.NDArray[np.uint32],\n        signal_chunk_counts: npt.NDArray[np.uint32],\n    ) -> None: ...\n    def add_run_info(\n        self,\n        acquisition_id: str,\n        acquisition_start_time: int,\n        adc_max: int,\n        adc_min: int,\n        context_tags: List[Tuple[str, str]],\n        experiment_name: str,\n        flow_cell_id: str,\n        flow_cell_product_code: str,\n        protocol_name: str,\n        protocol_run_id: str,\n        protocol_start_time: int,\n        sample_id: str,\n        sample_rate: int,\n        sequencing_kit: str,\n        sequencer_position: str,\n        sequencer_position_type: str,\n        software: str,\n        system_name: str,\n        system_type: str,\n        tracking_id: List[Tuple[str, str]],\n    ) -> int: ...\n    def close(self) -> None: ...\n\nclass FileWriterOptions:\n    max_signal_chunk_size: int\n    read_table_batch_size: int\n    signal_compression_type: Any\n    signal_table_batch_size: int\n    def __init__(self, *args, **kwargs) -> None: ...\n\nclass Pod5AsyncSignalLoader:\n    def __init__(self, *args, **kwargs) -> None: ...\n    def release_next_batch(self) -> Pod5SignalCacheBatch: ...\n\nclass Pod5FileReader:\n    def __init__(self, *args, **kwargs) -> None: ...\n    def batch_get_signal(\n        self, get_samples: bool, get_sample_count: bool\n    ) -> Pod5AsyncSignalLoader: ...\n    def batch_get_signal_batches(\n        self,\n        get_samples: bool,\n        get_samples_count: bool,\n        batches: npt.NDArray[np.uint32],\n    ) -> Pod5AsyncSignalLoader: ...\n    def batch_get_signal_selection(\n        self,\n        get_samples: bool,\n        get_sample_count: bool,\n        batch_counts: npt.NDArray[np.uint32],\n        batch_rows: npt.NDArray[np.uint32],\n    ) -> Pod5AsyncSignalLoader: ...\n    def close(self) -> None: ...\n    def get_file_read_table_location(self) -> EmbeddedFileData: ...\n    def get_file_run_info_table_location(self) -> EmbeddedFileData: ...\n    def get_file_signal_table_location(self) -> EmbeddedFileData: ...\n    def get_file_version_pre_migration(self) -> str: ...\n    def plan_traversal(\n        self,\n        read_id_data: npt.NDArray[np.uint8],\n        batch_counts: npt.NDArray[np.uint32],\n        batch_rows: npt.NDArray[np.uint32],\n    ) -> int: ...\n\nclass Pod5RepackerOutput:\n    def __init__(self, *args, **kwargs) -> None: ...\n\nclass Pod5SignalCacheBatch:\n    def __init__(self, *args, **kwargs) -> None: ...\n    @property\n    def batch_index(self) -> int: ...\n    @property\n    def sample_count(self) -> npt.NDArray[np.uint64]: ...\n    @property\n    def samples(self) -> List[npt.NDArray[np.int16]]: ...\n\nclass RecoverFileOptions:\n    cleanup: bool\n    file_writer_options: FileWriterOptions\n    def __init__(self) -> None: ...\n\nclass RecoveredRowCounts:\n    reads: int\n    run_info: int\n    signal: int\n    def __init__(self) -> None: ...\n\nclass RecoveryDetails:\n    cleanup_errors: list[CleanupError]\n    row_counts: RecoveredRowCounts\n    def __init__(self) -> None: ...\n\nclass Repacker:\n    def __init__(self) -> None: ...\n    def add_all_reads_to_output(\n        self, output: Pod5RepackerOutput, input: Pod5FileReader\n    ) -> None: ...\n    def add_output(\n        self, output: FileWriter, check_duplicate_read_ids: bool\n    ) -> Pod5RepackerOutput: ...\n    def add_selected_reads_to_output(\n        self,\n        output: Pod5RepackerOutput,\n        input: Pod5FileReader,\n        batch_counts: npt.NDArray[np.uint32],\n        all_batch_rows: npt.NDArray[np.uint32],\n    ) -> None: ...\n    def finish(self) -> None: ...\n    @property\n    def is_complete(self) -> bool: ...\n    @property\n    def open_file_readers(self) -> int: ...\n    @property\n    def reads_completed(self) -> int: ...\n\ndef compress_signal(\n    signal: npt.NDArray[np.int16], compressed_signal_out: npt.NDArray[np.uint8]\n) -> int: ...\ndef create_file(\n    src_filename: str, writer_name: str, options: Optional[FileWriterOptions]\n) -> FileWriter: ...\ndef recover_file(\n    src_filename: str, dest_filename: str, options: Optional[RecoverFileOptions]\n) -> RecoveryDetails: ...\ndef decompress_signal(\n    compressed_signal: Union[npt.NDArray[np.uint8], memoryview],\n    signal_out: npt.NDArray[np.int16],\n) -> None: ...\ndef format_read_id_to_str(\n    read_id_data_out: npt.NDArray[np.uint8],\n) -> List[str]: ...\ndef get_error_string() -> str: ...\ndef load_read_id_iterable(\n    read_ids_str: Iterable, read_id_data_out: npt.NDArray[np.uint8]\n) -> int: ...\ndef open_file(filename: str) -> Pod5FileReader: ...\ndef update_file(reader: Pod5FileReader, output: str): ...\ndef vbz_compressed_signal_max_size(sample_count: int) -> int: ...\n"
  },
  {
    "path": "python/lib_pod5/src/lib_pod5/py.typed",
    "content": ""
  },
  {
    "path": "python/lib_pod5/src/test/test_lib_pod5.py",
    "content": "\"\"\"\nBasic lib pod5 tets\n\"\"\"\n\nfrom pathlib import Path\n\nfrom lib_pod5 import Pod5FileReader, create_file, open_file\n\n\ndef test_create_file(tmp_path: Path) -> None:\n    \"\"\"Test that a lib-pod5 can create a pod5 file\"\"\"\n\n    target = tmp_path / \"test.pod5\"\n    assert tmp_path.exists()\n    assert not target.exists()\n\n    create_file(str(target), \"test\")\n    assert target.exists()\n\n\ndef test_open_file(tmp_path: Path) -> None:\n    \"\"\"Test that a lib-pod5 can create and re-open a pod5 file\"\"\"\n    target = tmp_path / \"test.pod5\"\n    create_file(str(target), \"test\")\n    assert target.exists()\n\n    reader = open_file(str(target))\n    assert isinstance(reader, Pod5FileReader)\n\n    reader.close()\n"
  },
  {
    "path": "python/pod5/Makefile",
    "content": "SHELL = /bin/bash\nPYTHON ?= python3.7\n\nenvDir = venv\nenvPrompt ?= \"pod5\"\n\n.PHONY: clean install update docs\n\n# Clean the python virtual environment\nclean:\n\trm -rf ${envDir}\n\n# Completely install the python environment for development\ninstall: clean\n\t${PYTHON} -m venv --prompt=${envPrompt} ${envDir}\n\tsource ${envDir}/bin/activate \\\n\t&& pip install --upgrade pip \\\n\t&& pip install -e .[dev] \\\n\t&& pre-commit install\n\n\t@echo \"To activate your new environment:  source ${envDir}/bin/activate\"\n\n# Re-install the pod5 environments to refresh / update the environment with changes\nupdate:\n\tsource ${envDir}/bin/activate \\\n\t&& pip install -e .[dev]\n\n\t@echo \"Updated python environment\"\n"
  },
  {
    "path": "python/pod5/README.md",
    "content": "# POD5 Python Package\n\nThe `pod5` Python package contains the tools and python API wrapping the compiled bindings\nfor the POD5 file format from `lib_pod5`.\n\n## Installation\n\nThe `pod5` package is available on [pypi](https://pypi.org/project/pod5/) and is\ninstalled using `pip`:\n\n``` console\n  > pip install pod5\n```\n\n## Usage\n\n### Reading a POD5 File\n\nTo read a `pod5` file provide the the `Reader` class with the input `pod5` file path\nand call `Reader.reads()` to iterate over read records in the file. The example below\nprints the read_id of every record in the input `pod5` file.\n\n``` python\nimport pod5 as p5\n\nwith p5.Reader(\"example.pod5\") as reader:\n    for read_record in reader.reads():\n        print(read_record.read_id)\n```\n\nTo iterate over a selection of read_ids supply `Reader.reads()` with a collection\nof read_ids which must be `UUID` compatible:\n\n``` python\nimport pod5 as p5\n\n# Create a collection of read_id UUIDs\nread_ids: List[str] = [\n  \"00445e58-3c58-4050-bacf-3411bb716cc3\",\n  \"00520473-4d3d-486b-86b5-f031c59f6591\",\n]\n\nwith p5.Reader(\"example.pod5\") as reader:\n    for read_record in reader.reads(selection=read_ids):\n        assert str(read_record.read_id) in read_ids\n```\n\n### Plotting Signal Data Example\n\nHere is an example of how a user may plot a read’s signal data against time.\n\n``` python\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nimport pod5 as p5\n\n# Using the example pod5 file provided\nexample_pod5 = \"test_data/multi_fast5_zip.pod5\"\nselected_read_id = '0000173c-bf67-44e7-9a9c-1ad0bc728e74'\n\nwith p5.Reader(example_pod5) as reader:\n\n    # Read the selected read from the pod5 file\n    # next() is required here as Reader.reads() returns a Generator\n    read = next(reader.reads(selection=[selected_read_id]))\n\n    # Get the signal data and sample rate\n    sample_rate = read.run_info.sample_rate\n    signal = read.signal\n\n    # Compute the time steps over the sampling period\n    time = np.arange(len(signal)) / sample_rate\n\n    # Plot using matplotlib\n    plt.plot(time, signal)\n```\n\n### Writing a POD5 File\n\nThe `pod5` package provides the functionality to write POD5 files.\n\nIt is strongly recommended that users first look at the available tools when\nmanipulating existing datasets, as there may already be a tool to meet your needs.\nNew tools may be added to support our users and if you have a suggestion for a\nnew tool or feature please submit a request on the\n[pod5-file-format GitHub issues page](https://github.com/nanoporetech/pod5-file-format/issues).\n\nBelow is an example of how one may add reads to a new POD5 file using the `Writer`\nand its `add_read()` method.\n\n```python\nimport pod5 as p5\n\n# Populate container classes for read metadata\npore = p5.Pore(channel=123, well=3, pore_type=\"pore_type\")\ncalibration = p5.Calibration(offset=0.1, scale=1.1)\nend_reason = p5.EndReason(name=p5.EndReasonEnum.SIGNAL_POSITIVE, forced=False)\nrun_info = p5.RunInfo(\n    acquisition_id = ...\n    acquisition_start_time = ...\n    adc_max = ...\n    ...\n)\nsignal = ... # some signal data as numpy np.int16 array\n\nread = p5.Read(\n    read_id=UUID(\"0000173c-bf67-44e7-9a9c-1ad0bc728e74\"),\n    end_reason=end_reason,\n    calibration=calibration,\n    pore=pore,\n    run_info=run_info,\n    ...\n    signal=signal,\n)\n\nwith p5.Writer(\"example.pod5\") as writer:\n    # Write the read object\n    writer.add_read(read)\n```\n\n## Tools\n\n1. [pod5 view](#pod5-view)\n2. [pod5 inspect](#pod5-inspect)\n3. [pod5 merge](#pod5-merge)\n4. [pod5 filter](#pod5-filter)\n5. [pod5 subset](#pod5-subset)\n6. [pod5 repack](#pod5-repack)\n7. [pod5 recover](#pod5-recover)\n8. [pod5 convert fast5](#pod5-convert-fast5)\n9. [pod5 convert to_fast5](#pod5-convert-to_fast5)\n10. [pod5 update](#pod5-update)\n\nThe ``pod5`` package provides the following tools for inspecting and manipulating\nPOD5 files as well as converting between ``.pod5`` and ``.fast5`` file formats.\n\nTo disable the `tqdm <https://github.com/tqdm/tqdm>`_  progress bar set the environment\nvariable ``POD5_PBAR=0``.\n\nTo enable debugging output which may also output detailed log files, set the environment\nvariable ``POD5_DEBUG=1``\n\n### Pod5 View\n\nThe ``pod5 view`` tool is used to produce a table similarr to a sequencing summary\nfrom the contents of ``.pod5`` files. The default output is a tab-separated table\nwritten to stdout with all available fields.\n\nThis tools is indented to replace ``pod5 inspect reads`` and is over 200x faster.\n\n``` bash\n> pod5 view --help\n\n# View the list of fields with a short description in-order (shortcut -L)\n> pod5 view --list-fields\n\n# Write the summary to stdout\n> pod5 view input.pod5\n\n# Write the summary of multiple pod5s to a file\n> pod5 view *.pod5 --output summary.tsv\n\n# Write the summary as a csv\n> pod5 view *.pod5 --output summary.csv --separator ','\n\n# Write only the read_ids with no header (shorthand -IH)\n> pod5 view input.pod5 --ids --no-header\n\n# Write only the listed fields\n# Note: The field order is fixed the order shown in --list-fields\n> pod5 view input.pod5 --include \"read_id, channel, num_samples, end_reason\"\n\n# Exclude some unwanted fields\n> pod5 view input.pod5 --exclude \"filename, pore_type\"\n```\n\n### Pod5 inspect\n\nThe ``pod5 inspect`` tool can be used to extract details and summaries of\nthe contents of ``.pod5`` files. There are two programs for users within ``pod5 inspect``\nand these are read and reads\n\n``` bash\n> pod5 inspect --help\n> pod5 inspect {reads, read, summary} --help\n```\n\n#### Pod5 inspect reads\n\n> :warning: This tool is deprecated and has been replaced by ``pod5 view`` which is significantly faster.\n\nInspect all reads and print a csv table of the details of all reads in the given ``.pod5`` files.\n\n``` bash\n> pod5 inspect reads pod5_file.pod5\n\n  read_id,channel,well,pore_type,read_number,start_sample,end_reason,median_before,calibration_offset,calibration_scale,sample_count,byte_count,signal_compression_ratio\n  00445e58-3c58-4050-bacf-3411bb716cc3,908,1,not_set,100776,374223800,signal_positive,205.3,-240.0,0.1,65582,58623,0.447\n  00520473-4d3d-486b-86b5-f031c59f6591,220,1,not_set,7936,16135986,signal_positive,192.0,-233.0,0.1,167769,146495,0.437\n    ...\n```\n\n#### Pod5 inspect read\n\nInspect the pod5 file, find a specific read and print its details.\n\n``` console\n> pod5 inspect read pod5_file.pod5 00445e58-3c58-4050-bacf-3411bb716cc3\n\n  File: out-tmp/output.pod5\n  read_id: 0e5d6827-45f6-462c-9f6b-21540eef4426\n  read_number:    129227\n  start_sample:   367096601\n  median_before:  171.889404296875\n  channel data:\n  channel: 2366\n  well: 1\n  pore_type: not_set\n  end reason:\n  name: signal_positive\n  forced False\n  calibration:\n  offset: -243.0\n  scale: 0.1462070643901825\n  samples:\n  sample_count: 81040\n  byte_count: 71989\n  compression ratio: 0.444\n  run info\n      acquisition_id: 2ca00715f2e6d8455e5174cd20daa4c38f95fae2\n      acquisition_start_time: 2021-07-23 13:48:59.780000\n      adc_max: 0\n      adc_min: 0\n      context_tags\n      barcoding_enabled: 0\n      basecall_config_filename: dna_r10.3_450bps_hac_prom.cfg\n      experiment_duration_set: 2880\n      ...\n```\n\n### Pod5 merge\n\n``pod5 merge`` is a tool for merging multiple  ``.pod5`` files into one monolithic pod5 file.\n\nThe contents of the input files are checked for duplicate read_ids to avoid\naccidentally merging identical reads. To override this check set the argument\n``-D / --duplicate-ok``\n\n``` bash\n# View help\n> pod5 merge --help\n\n# Merge a pair of pod5 files\n> pod5 merge example_1.pod5 example_2.pod5 --output merged.pod5\n\n# Merge a glob of pod5 files\n> pod5 merge *.pod5 -o merged.pod5\n\n# Merge a glob of pod5 files ignoring duplicate read ids\n> pod5 merge *.pod5 -o merged.pod5 --duplicate-ok\n```\n\n### Pod5 filter\n\n``pod5 filter`` is a simpler alternative to ``pod5 subset`` where reads are subset from\none or more input ``.pod5`` files using a list of read ids provided using the ``--ids`` argument\nand writing those reads to a *single* ``--output`` file.\n\nSee ``pod5 subset`` for more advanced subsetting.\n\n``` bash\n> pod5 filter example.pod5 --output filtered.pod5 --ids read_ids.txt\n```\n\nThe ``--ids`` selection text file must be a simple list of valid UUID read_ids with\none read_id per line. Only records which match the UUID regex (lower-case) are used.\nLines beginning with a ``#`` (hash / pound symbol) are interpreted as comments.\nEmpty lines are not valid and may cause errors during parsing.\n\n> The ``filter`` and ``subset`` tools will assert that any requested read_ids are\n> present in the inputs. If a requested read_id is missing from the inputs\n> then the tool will issue the following error:\n>\n> ``` bash\n> POD5 has encountered an error: 'Missing read_ids from inputs but --missing-ok not set'\n> ```\n>\n> To disable this warning then the '-M / --missing-ok' argument.\n\nWhen supplying multiple input files to 'filter' or 'subset', the tools is\neffectively performing a ``merge`` operation. The 'merge' tool is better suited\nfor handling very large numbers of input files.\n\n#### Example filtering pipeline\n\nThis is a trivial example of how to select a random sample of 1000 read_ids from a\npod5 file using ``pod5 view`` and ``pod5 filter``.\n\n``` bash\n# Get a random selection of read_ids\n> pod5 view all.pod5 --ids --no-header --output all_ids.txt\n> all_ids.txt sort --random-sort | head --lines 1000 > 1k_ids.txt\n\n# Filter to that selection\n> pod5 filter all.pod5 --ids 1k_ids.txt --output 1k.pod5\n\n# Check the output\n> pod5 view 1k.pod5 -IH | wc -l\n1000\n```\n\n### Pod5 subset\n\n``pod5 subset`` is a tool for subsetting reads in ``.pod5`` files into one or more\noutput ``.pod5`` files. See also ``pod5 filter``\n\nThe ``pod5 subset`` tool requires a *mapping* which defines which read_ids should be\nwritten to which output. There are multiple ways of specifying this mapping which are\ndefined in either a ``.csv`` file or by using a ``--table`` (csv or tsv)\nand instructions on how to interpret it.\n\n``pod5 subset`` aims to be a generic tool to subset from multiple inputs to multiple outputs.\nIf your use-case is to ``filter`` read_ids from one or more inputs into a single output\nthen ``pod5 filter`` might be a more appropriate tool as the only input is a list of read_ids.\n\n``` bash\n# View help\n> pod5 subset --help\n\n# Subset input(s) using a pre-defined mapping\n> pod5 subset example_1.pod5 --csv mapping.csv\n\n# Subset input(s) using a dynamic mapping created at runtime\n> pod5 subset example_1.pod5 --table table.txt --columns barcode\n```\n\n> Care should be taken to ensure that when providing multiple input ``.pod5`` files to ``pod5 subset``\n> that there are no read_id UUID clashes. If a duplicate read_id is detected an exception\n> will be raised unless the ``--duplicate-ok`` argument is set. If ``--duplicate-ok`` is\n> set then both reads will be written to the output, although this is not recommended.\n\n#### Note on positional arguments\n\n> The ``--columns`` argument will greedily consume values and as such, care should be taken\n> with the placement of any positional arguments. The following line will result in an error\n> as the input pod5 file is consumed by ``--columns`` resulting in no input file being set.\n\n```bash\n# Invalid placement of positional argument example.pod5\n$ pod5 subset --table table.txt --columns barcode example.pod5\n```\n\n#### Creating a Subset Mapping\n\n##### Target Mapping (.csv)\n\nThe example below shows a ``.csv`` subset target mapping. Any lines (e.g. header line)\nwhich do not have a read_id which matches the UUID regex (lower-case) in the second\ncolumn is ignored.\n\n``` text\ntarget, read_id\noutput_1.pod5,132b582c-56e8-4d46-9e3d-48a275646d3a\noutput_1.pod5,12a4d6b1-da6e-4136-8bb3-1470ef27e311\noutput_2.pod5,0ff4dc01-5fa4-4260-b54e-1d8716c7f225\noutput_2.pod5,0e359c40-296d-4edc-8f4a-cca135310ab2\noutput_2.pod5,0e9aa0f8-99ad-40b3-828a-45adbb4fd30c\n```\n\n##### Target Mapping from Table\n\n``pod5 subset`` can dynamically generate output targets and collect associated reads\nbased on a text file containing a table (csv or tsv) parsible by ``polars``.\nThis table file could be the output from ``pod5 view`` or from a sequencing summary.\nThe table must contain a header row and a series of columns on which to group unique\ncollections of values. Internally this process uses the\n`polars.Dataframe.group_by <https://pola-rs.github.io/polars/py-polars/html/reference/dataframe/api/polars.DataFrame.group_by.html>`_\nfunction where the ``by`` parameter is the sequence of column names specified with\nthe ``--columns`` argument.\n\nGiven the following example ``--table`` file, observe the resultant outputs given various\narguments:\n\n``` text\nread_id    mux    barcode      length\nread_a     1      barcode_a    4321\nread_b     1      barcode_b    1000\nread_c     2      barcode_b    1200\nread_d     2      barcode_c    1234\n```\n\n``` bash\n> pod5 subset example_1.pod5 --output barcode_subset --table table.txt --columns barcode\n> ls barcode_subset\nbarcode-barcode_a.pod5     # Contains: read_a\nbarcode-barcode_b.pod5     # Contains: read_b, read_c\nbarcode-barcode_c.pod5     # Contains: read_d\n\n> pod5 subset example_1.pod5 --output mux_subset --table table.txt --columns mux\n> ls mux_subset\nmux-1.pod5     # Contains: read_a, read_b\nmus-2.pod5     # Contains: read_c, read_d\n\n> pod5 subset example_1.pod5 --output barcode_mux_subset --table table.txt --columns barcode mux\n> ls barcode_mux_subset\nbarcode-barcode_a_mux-1.pod5    # Contains: read_a\nbarcode-barcode_b_mux-1.pod5    # Contains: read_b\nbarcode-barcode_b_mux-2.pod5    # Contains: read_c\nbarcode-barcode_c_mux-2.pod5    # Contains: read_d\n```\n\n##### Output Filename Templating\n\nWhen subsetting using a table the output filename is generated from a template\nstring. The automatically generated template is the sequential concatenation of\n``column_name-column_value`` followed by the ``.pod5`` file extension.\n\nThe user can set their own filename template using the ``--template`` argument.\nThis argument accepts a string in the `Python f-string style <https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals>`_\nwhere the subsetting variables are used for keyword placeholder substitution.\nKeywords should be placed within curly-braces. For example:\n\n``` bash\n# default template used = \"barcode-{barcode}.pod5\"\n> pod5 subset example_1.pod5 --output barcode_subset --table table.txt --columns barcode\n\n# default template used = \"barcode-{barcode}_mux-{mux}.pod5\"\n> pod5 subset example_1.pod5 --output barcode_mux_subset --table table.txt --columns barcode mux\n\n> pod5 subset example_1.pod5 --output barcode_subset --table table.txt --columns barcode --template \"{barcode}.subset.pod5\"\n> ls barcode_subset\nbarcode_a.subset.pod5    # Contains: read_a\nbarcode_b.subset.pod5    # Contains: read_b, read_c\nbarcode_c.subset.pod5    # Contains: read_d\n```\n\n##### Example subsetting from ``pod5 inspect reads``\n\nThe ``pod5 inspect reads`` tool will output a csv table summarising the content of the\nspecified ``.pod5`` file which can be used for subsetting. The example below shows\nhow to split a ``.pod5`` file by the well field.\n\n``` bash\n# Create the csv table from inspect reads\n> pod5 inspect reads example.pod5 > table.csv\n> pod5 subset example.pod5 --table table.csv --columns well\n```\n\n### Pod5 repack\n\n``pod5 repack`` will simply repack ``.pod5`` files into one-for-one output files of the same name.\n\n``` bash\n> pod5 repack pod5s/*.pod5 repacked_pods/\n```\n\n### Pod5 Recover\n\n``pod5 recover`` will attempt to recover data from corrupted or truncated ``.pod5`` files\nby copying all valid table batches and cleanly closing the new files. New files are written\nas siblings to the inputs with the `_recovered.pod5` suffix.\n\n``` bash\n> pod5 recover --help\n> pod5 recover broken.pod5\n> ls\nbroken.pod5 broken_recovered.pod5\n```\n\n### pod5 convert fast5\n\nThe ``pod5 convert fast5`` tool takes one or more ``.fast5`` files and converts them\nto one or more ``.pod5`` files.\n\nIf the tool detects single-read fast5 files, please convert them into multi-read\nfast5 files using the tools available in the ``ont_fast5_api`` project.\n\nThe progress bar shown during conversion assumes the number of reads in an input\n``.fast5`` is 4000. The progress bar will update the total value during runtime if\nrequired.\n\n> Some content previously stored in ``.fast5`` files is **not** compatible with the POD5\n> format and will not be converted. This includes all analyses stored in the\n> ``.fast5`` file.\n>\n> Please ensure that any other data is recovered from ``.fast5`` before deletion.\n\nBy default ``pod5 convert fast5`` will show exceptions raised during conversion as *warnings*\nto the user. This is to gracefully handle potentially corrupt input files or other\nruntime errors in long-running conversion tasks. The ``--strict`` argument allows\nusers to opt-in to strict runtime assertions where any exception raised will promptly\nstop the conversion process with an error.\n\n``` bash\n# View help\n> pod5 convert fast5 --help\n\n# Convert fast5 files into a monolithic output file\n> pod5 convert fast5 ./input/*.fast5 --output converted.pod5\n\n# Convert fast5 files into a monolithic output in an existing directory\n> pod5 convert fast5 ./input/*.fast5 --output outputs/\n> ls outputs/\noutput.pod5 # default name\n\n# Convert each fast5 to its relative converted output. The output files are written\n# into the output directory at paths relatve to the path given to the\n# --one-to-one argument. Note: This path must be a relative parent to all\n# input paths.\n> ls input/*.fast5\nfile_1.fast5 file_2.fast5 ... file_N.fast5\n> pod5 convert fast5 ./input/*.fast5 --output output_pod5s/ --one-to-one ./input/\n> ls output_pod5s/\nfile_1.pod5 file_2.pod5 ... file_N.pod5\n\n# Note the different --one-to-one path which is now the current working directory.\n# The new sub-directory output_pod5/input is created.\n> pod5 convert fast5 ./input/*.fast5 output_pod5s --one-to-one ./\n> ls output_pod5s/\ninput/file_1.pod5 input/file_2.pod5 ... input/file_N.pod5\n\n# Convert all inputs so that they have neibouring pod5 in current directory\n> pod5 convert fast5 *.fast5 --output . --one-to-one .\n> ls\nfile_1.fast5 file_1.pod5 file_2.fast5 file_2.pod5  ... file_N.fast5 file_N.pod5\n\n# Convert all inputs so that they have neibouring pod5 files from a parent directory\n> pod5 convert fast5 ./input/*.fast5 --output ./input/ --one-to-one ./input/\n> ls input/*\nfile_1.fast5 file_1.pod5 file_2.fast5 file_2.pod5  ... file_N.fast5 file_N.pod5\n```\n\n### Pod5 convert to_fast5\n\nThe ``pod5 convert to_fast5`` tool takes one or more ``.pod5`` files and converts them\nto multiple ``.fast5`` files. The default behaviour is to write 4000 reads per output file\nbut this can be controlled with the ``--file-read-count`` argument.\n\n``` bash\n# View help\n> pod5 convert to_fast5 --help\n\n# Convert pod5 files to fast5 files with default 4000 reads per file\n> pod5 convert to_fast5 example.pod5 --output pod5_to_fast5/\n> ls pod5_to_fast5/\noutput_1.fast5 output_2.fast5 ... output_N.fast5\n```\n\n### Pod5 Update\n\nThe ``pod5 update`` tools is used to update old pod5 files to use the latest schema.\nCurrently the latest schema version is version 3.\n\nFiles are written into the ``--output`` directory with the same name.\n\n``` bash\n> pod5 update --help\n\n# Update a named files\n> pod5 update my.pod5 --output updated/\n> ls updated\nupdated/my.pod5\n\n# Update an entire directory\n> pod5 update old/ -o updated/\n```\n"
  },
  {
    "path": "python/pod5/examples/find_all_reads.py",
    "content": "#!/usr/bin/python3\n\nimport argparse\nfrom pathlib import Path\n\nimport pod5 as p5\n\n\ndef main():\n    parser = argparse.ArgumentParser(\"Iterate through all read ids in an pod5 file\")\n    parser.add_argument(\"input\", type=Path)\n    args = parser.parse_args()\n\n    with p5.Reader(args.input) as reader:\n        for read in reader.reads():\n            print(f\"Found read {read.read_id}\")\n            print(f\"  Read has  {read.sample_count} samples\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python/pod5/examples/find_specific_reads.py",
    "content": "#!/usr/bin/python3\n\nimport argparse\nfrom pathlib import Path\nfrom uuid import UUID\n\nimport pandas as pd\n\nimport pod5 as p5\n\n\ndef main():\n    parser = argparse.ArgumentParser(\n        \"Iterate through specific read ids in an pod5 file\"\n    )\n    parser.add_argument(\"input\", type=Path)\n    parser.add_argument(\"read_ids_csv\", type=Path)\n    args = parser.parse_args()\n\n    read_ids_to_find = [UUID(r) for r in pd.read_csv(args.read_ids_csv)[\"read_id\"]]\n\n    with p5.Reader(args.input) as reader:\n        for read in reader.reads(read_ids_to_find):\n            print(f\"Found read {read.read_id}\")\n            print(f\"  Read has  {read.sample_count} samples\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python/pod5/pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools >= 61.0\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n\n[project]\nname = \"pod5\"\nauthors = [\n  { name = \"Oxford Nanopore Technologies plc\", email = \"support@nanoporetech.com\" },\n]\nreadme = \"README.md\"\nrequires-python=\">= 3.9, < 4.0\"\ndescription = \"Oxford Nanopore Technologies Pod5 File Format Python API and Tools\"\ndynamic = [\"version\"]\nkeywords = ['nanopore']\nclassifiers = [\n  'Environment :: Console',\n  'Intended Audience :: Developers',\n  'Intended Audience :: Science/Research',\n  'Natural Language :: English',\n  'Programming Language :: Python :: 3',\n  'Topic :: Scientific/Engineering :: Bio-Informatics',\n]\nlicense=\"MPL-2.0\"\n\ndependencies = [\n  \"deprecated ~= 1.2.18\",\n  \"lib_pod5 == 0.3.39\",\n  \"iso8601\",\n  \"more_itertools\",\n  \"numpy >= 1.21.0\",\n  'typing-extensions; python_version<\"3.10\"',\n  'pyarrow ~= 22.0.0; python_version>=\"3.10\"',\n  'pyarrow ~= 18.0.0; python_version<\"3.10\"',\n  \"pytz\",\n  \"packaging\",\n  \"polars ~= 1.30\",\n  'h5py ~= 3.11',\n  \"vbz_h5py_plugin\",\n  \"tqdm\",\n]\n\n[project.optional-dependencies]\ndev = [\n  \"black == 23.3.0\",\n  \"mypy == 1.3.0\",\n  \"pre-commit==v2.21.0\",\n  \"psutil\",\n  \"pytest ~= 7.3\",\n  \"pytest-cov ~= 4.0\",\n  \"pytest-mock\",\n  \"types-Deprecated\",\n  \"types-setuptools\",\n  \"types-pytz\",\n]\n\n[project.scripts]\npod5 = \"pod5.tools.main:main\"\n\n[project.urls]\nHomepage = \"https://github.com/nanoporetech/pod5-file-format\"\nIssues = \"https://github.com/nanoporetech/pod5-file-format/issues\"\nDocumentation = \"https://pod5-file-format.readthedocs.io/en/latest/\"\n\n[tool.setuptools.packages.find]\nwhere = [\"src\"]\nexclude = [\"test*\"]\n\n[tool.setuptools.dynamic]\nversion = { attr = \"pod5._version.__version__\" }\n\n[tool.black]\ntarget-version = [\"py37\"]\n"
  },
  {
    "path": "python/pod5/setup.py",
    "content": "\"\"\"\npod5 setup.py\nProprietary and confidential information of Oxford Nanopore Technologies plc\nAll rights reserved; (c)2022: Oxford Nanopore Technologies plc\n\nThis script can either install a development version of pod5 to the current\nPython environment, or create a Python wheel.\n\n\"\"\"\n\nimport setuptools\n\nif __name__ == \"__main__\":\n    setuptools.setup()\n"
  },
  {
    "path": "python/pod5/src/pod5/__init__.py",
    "content": "\"\"\"POD5 Format\n\nBindings for the POD5 file format\n\"\"\"\n\n# Pull the version from the pyproject.toml\nimport sys\n\nif sys.version_info >= (3, 8):\n    from importlib import metadata\nelse:\n    import importlib_metadata as metadata\n\n__version__ = metadata.version(\"pod5\")\n\nfrom .api_utils import (\n    format_read_id_to_str,\n    format_read_ids,\n    load_read_id_iterable,\n    pack_read_ids,\n)\nfrom .pod5_types import (\n    Calibration,\n    CompressedRead,\n    EndReason,\n    EndReasonEnum,\n    Pore,\n    Read,\n    RunInfo,\n)\nfrom .reader import Reader, ReadRecord, ReadRecordBatch\nfrom .dataset import DatasetReader\nfrom .signal_tools import (\n    vbz_compress_signal,\n    vbz_decompress_signal,\n    vbz_decompress_signal_chunked,\n    vbz_decompress_signal_into,\n)\nfrom .writer import SignalType, Writer\n\n__all__ = (\n    \"__version__\",\n    \"format_read_id_to_str\",\n    \"format_read_ids\",\n    \"load_read_id_iterable\",\n    \"pack_read_ids\",\n    \"DatasetReader\",\n    \"Calibration\",\n    \"CompressedRead\",\n    \"EndReason\",\n    \"EndReasonEnum\",\n    \"Pore\",\n    \"Read\",\n    \"RunInfo\",\n    \"Reader\",\n    \"ReadRecord\",\n    \"ReadRecordBatch\",\n    \"SignalType\",\n    \"vbz_compress_signal\",\n    \"vbz_decompress_signal\",\n    \"vbz_decompress_signal_chunked\",\n    \"vbz_decompress_signal_into\",\n    \"Writer\",\n)\n"
  },
  {
    "path": "python/pod5/src/pod5/api_utils.py",
    "content": "\"\"\"\nUtility functions for the pod5 API\n\"\"\"\n\nimport warnings\nfrom typing import Any, Collection, List, Union\n\nimport numpy as np\nimport numpy.typing as npt\nimport pyarrow as pa\nfrom lib_pod5 import format_read_id_to_str, load_read_id_iterable\n\n\nclass Pod5ApiException(Exception):\n    \"\"\"Generic Pod5 API Exception\"\"\"\n\n\ndef pack_read_ids(\n    read_ids: Collection[str], invalid_ok: bool = False\n) -> npt.NDArray[np.uint8]:\n    \"\"\"\n    Convert a `Collection` of `read_id` strings to a `numpy.ndarray`\n    in preparation for writing to pod5 files.\n\n    Parameters\n    ----------\n    read_ids : Collection[str]\n        Collection of well-formatted read_id strings\n\n    Returns\n    -------\n    packed_read_ids : numpy.ndarray[uint8]\n        Repacked read_ids ready for writing to pod5 files.\n    \"\"\"\n    read_id_data = np.empty(shape=(len(read_ids), 16), dtype=np.uint8)\n    count = load_read_id_iterable(read_ids, read_id_data)\n    if invalid_ok is False and count != len(read_ids):\n        raise RuntimeError(\"Invalid read id passed\")\n\n    return read_id_data[:count]\n\n\ndef format_read_ids(\n    read_ids: Union[npt.NDArray[np.uint8], pa.lib.FixedSizeBinaryArray],\n) -> List[str]:\n    \"\"\"\n    Convert a packed array of read_ids and convert them to a list of strings.\n\n    Parameters\n    ----------\n    read_ids : numpy.ndarray[uint8], pa.lib.FixedSizeBinaryArray\n        Packed read_ids from a numpy.ndarray or read directly from pod5 file\n\n    Returns\n    -------\n    read_ids : list[str]\n        A list of converted read_ids as strings\n    \"\"\"\n    if isinstance(read_ids, pa.lib.FixedSizeBinaryArray):\n        read_ids = read_ids.buffers()[1]\n    return format_read_id_to_str(read_ids)\n\n\ndef deprecation_warning(deprecated: str, alternate: str) -> None:\n    \"\"\"\n    Issue a `FutureWarning` warning that `deprecated` has been deprecated in favour of\n    `alternate`.\n\n    Parameters\n    ----------\n    deprecated : str\n        The module path to the deprecated item\n    alternate : str\n        The module path to the alternate item\n    \"\"\"\n    warnings.warn(\n        f\"{deprecated} is deprecated. Please use {alternate}\",\n        DeprecationWarning,\n        stacklevel=2,\n    )\n\n\ndef safe_close(obj: Any, attr: str) -> None:\n    \"\"\"\n    Try to close() an object's attribute ignoring any exceptions raised.\n    This is used to safely handle closing potentially unassigned attributes\n    while calling close() in __del__()\n    \"\"\"\n    if not hasattr(obj, attr):\n        return\n\n    try:\n        getattr(obj, attr).close()\n    except Exception:\n        pass\n"
  },
  {
    "path": "python/pod5/src/pod5/dataset.py",
    "content": "from concurrent.futures import Future, ThreadPoolExecutor, as_completed\nfrom functools import lru_cache, partial\nimport os\nfrom pathlib import Path\nfrom typing import (\n    Any,\n    Callable,\n    Collection,\n    Dict,\n    Generator,\n    Iterable,\n    List,\n    Optional,\n    Set,\n    Union,\n)\nimport warnings\nfrom pod5.api_utils import Pod5ApiException\n\nfrom pod5.pod5_types import PathOrStr\nfrom pod5.reader import ReadRecord, Reader\nfrom pod5.tools.utils import search_path\n\nDEFAULT_CPUS = min(os.cpu_count() or 1, 4)\n\n\nclass DatasetReader:\n    def __init__(\n        self,\n        paths: Union[PathOrStr, Collection[PathOrStr]],\n        recursive: bool = False,\n        pattern: str = \"*.pod5\",\n        index: bool = False,\n        threads: int = DEFAULT_CPUS,\n        max_cached_readers: Optional[int] = 2**4,\n        warn_duplicate_indexing: bool = True,\n    ) -> None:\n        \"\"\"\n        Reads pod5 files and/or directories of pod5 files as a dataset.\n\n        Parameters\n        ----------\n        paths : PathOrStr | Collection[PathOrStr]\n            One or more files or directories to load\n        recursive : bool\n            Search directories in `paths` recursively\n        pattern : str\n            A glob expression to match against file names\n        index : bool\n            Promptly index the dataset instead of deferring until required\n        threads : int\n            The number of threads to use\n        max_cached_readers :  Optional[int]\n            The maximum size of the `Reader` LRU cache. Set to `None` for an unlimited\n            cache size.\n        warn_duplicate_indexing : bool\n            Issue warnings when duplicate read_ids are detected and\n            indexing by read_id is attempted\n\n        Note\n        ----\n        Random record access is implemented by creating an index of read_id to file\n        path. This can consume a large amount of memory. Methods that generate an index\n        have this noted in their docstring.\n\n        Warnings\n        --------\n        If duplicate read_ids are present in the dataset, iterator methods such\n        as `reads()` will yield all copies. Indexing methods such as `get_read`\n        return one chosen randomly and issue a warning which can be suppressed by\n        setting `warn_duplicate_indexing=False`\n        \"\"\"\n        self._paths: List[Path] = sorted(\n            self._collect_dataset(\n                paths, recursive=recursive, pattern=pattern, threads=threads\n            )\n        )\n        self._num_reads: Optional[int] = None\n        self._max_cached_readers = max_cached_readers\n        self.threads = threads\n        self.warn_duplicate_indexing = warn_duplicate_indexing\n\n        # Cache on DatasetReader instances and control cache size on init\n        self._get_reader = self._init_get_reader(self._max_cached_readers)\n\n        if index:\n            self._index_read_ids()\n        else:\n            self._index: Optional[Dict[str, Path]] = None\n\n    def __iter__(self) -> Generator[ReadRecord, None, None]:\n        yield from self.reads()\n\n    def __len__(self) -> int:\n        \"\"\"Returns the number of reads in this dataset\"\"\"\n        return self.num_reads\n\n    @property\n    def num_reads(self) -> int:\n        \"\"\"\n        Return the number of `ReadRecords` in this dataset.\n        \"\"\"\n        if self._num_reads is not None:\n            return self._num_reads\n\n        def _get_num_reads(path: Path) -> int:\n            try:\n                return self.get_reader(path).num_reads\n            except Exception as exc:\n                msg = f\"DatasetReader error reading: {[path]}\"\n                raise Pod5ApiException(msg) from exc\n\n        self._num_reads = sum(\n            self._run_max_workers(_get_num_reads, self.paths, self.threads)\n        )\n        return self._num_reads\n\n    @property\n    def paths(self) -> List[Path]:\n        \"\"\"Return the list of pod5 file paths in this dataset\"\"\"\n        return self._paths\n\n    @property\n    def read_ids(self) -> Generator[str, None, None]:\n        \"\"\"\n        Yield all read_ids in this dataset\n        \"\"\"\n\n        def _get_read_ids(path: Path) -> List[str]:\n            return self.get_reader(path).read_ids\n\n        for ids in self._run_max_workers(_get_read_ids, self.paths, self.threads):\n            yield from ids\n\n    def reads(\n        self,\n        selection: Optional[Iterable[str]] = None,\n        preload: Optional[Set[str]] = None,\n    ) -> Generator[ReadRecord, None, None]:\n        \"\"\"\n        Iterate over ``ReadRecord``s in the dataset.\n\n        Parameters\n        ----------\n        selection : iterable[str]\n            The read ids to walk in the file.\n        preload : set[str]\n            Columns to preload - \"samples\" and \"sample_count\" are valid values\n\n        Note\n        ----\n        ``ReadRecord``s are yielded in on-disk record order for each file in ``self.paths``.\n\n        Missing records are not detected and multiple records will be\n        yielded if there are duplicates in either of the dataset or selection.\n\n        Yields\n        ------\n        ReadRecord\n        \"\"\"\n\n        def _get_reads_iter(path: Path) -> Generator[ReadRecord, None, None]:\n            return self.get_reader(path).reads(\n                selection=selection, missing_ok=True, preload=preload\n            )\n\n        for reads in self._run_max_workers(_get_reads_iter, self.paths, self.threads):\n            yield from reads\n\n    def get_read(self, read_id: str) -> Optional[ReadRecord]:\n        \"\"\"\n        Get a `ReadRecord` by `read_id` or return `None` if it is missing\n\n        Parameters\n        ----------\n        read_id : str\n            The read_id (UUID) string in this dataset to find\n\n        Note\n        ----\n        This method will index the dataset\n\n        Warnings\n        --------\n        Issues a warning if duplicate read_ids are detected in this dataset.\n        The returned `ReadRecord` is a always valid but the source may be random\n        between instances of a `DatasetReader`.\n\n        Returns\n        -------\n        Optional[ReadRecord]\n        \"\"\"\n        path = self.get_path(read_id)\n        if path is None:\n            return None\n\n        reader = self.get_reader(path)\n        try:\n            return next(reader.reads(selection=[read_id]))\n        except StopIteration:\n            return None\n\n    @staticmethod\n    def _init_get_reader(maxsize: Optional[int]) -> Callable[[Path], Reader]:\n        # This wrapper allows the size of the LRU cache to be set during initialization\n        # without global variables.\n\n        # Note that a Pod5.Reader consumes at least 4 file handles.\n        # If you experience \"Too Many Open Files\" reduce the `max_cached_readers` and `threads`\n        @lru_cache(maxsize=maxsize)\n        def _get_reader(path: Path) -> Reader:\n            return Reader(path)\n\n        return _get_reader\n\n    def get_reader(self, path: PathOrStr) -> Reader:\n        \"\"\"\n        Get a pod5 file `Reader` in this dataset by `path`\n\n        Parameters\n        ----------\n        path : PathOrStr\n            Path to a pod5 file\n\n        Returns\n        -------\n        Reader\n        \"\"\"\n        return self._get_reader(Path(path))\n\n    def get_path(self, read_id: str) -> Optional[Path]:\n        \"\"\"\n        Get the pod5 `Path` for a given `read_id` or `None` if it was not found\n\n        Parameters\n        ----------\n        read_id : str\n            The read_id (UUID) string in this dataset\n\n        Note\n        ----\n        This method will index the dataset\n\n        Warnings\n        --------\n        Issues a warning if duplicate read_ids are detected in this dataset.\n        The returned path is a always valid file which contains this read_id but this\n        may be random between instances.\n\n        Returns\n        -------\n        Optional[Path]\n        \"\"\"\n\n        self.index_read_ids()\n        if self._index is None:\n            return None\n\n        if self.has_duplicate():\n            self._issue_duplicate_read_warning()\n\n        return self._index.get(read_id, None)\n\n    def clear_readers(self) -> None:\n        \"\"\"Clears the readers LRU cache\"\"\"\n        self._get_reader.cache_clear()  # type: ignore\n\n    def clear_index(self) -> None:\n        \"\"\"Clears the read_id to file path index\"\"\"\n        self._index = None\n\n    def has_duplicate(self) -> bool:\n        \"\"\"\n        Returns `True` if there are duplicate `read_ids` in this dataset\n\n        Note\n        ----\n        This method will index the dataset\n        \"\"\"\n        self.index_read_ids()\n        assert self._index is not None\n        return len(self) != len(self._index)\n\n    @staticmethod\n    def _collect_dataset(\n        paths: Union[PathOrStr, Collection[PathOrStr]],\n        recursive: bool,\n        pattern: str,\n        threads: int,\n    ) -> Set[Path]:\n        if isinstance(paths, (str, Path, os.PathLike)):\n            paths = [paths]\n\n        if not isinstance(paths, Collection):\n            raise TypeError(\n                f\"paths must be a Collection[PathOrStr] but found {type(paths)=}\"\n            )\n\n        paths = [Path(p) for p in paths]\n        collected: Set[Path] = set()\n        with ThreadPoolExecutor(max_workers=threads) as executor:\n            search = partial(search_path, recursive=recursive, patterns=[pattern])\n            for coll in executor.map(search, paths):\n                collected.update(coll)\n        return collected\n\n    def index_read_ids(self) -> None:\n        \"\"\"\n        Performs read_id indexing if not already done.\n        \"\"\"\n        if self._index is None:\n            self._index_read_ids()\n        return\n\n    def _index_read_ids(self) -> None:\n        def _get_index(path: Path) -> Dict[str, Path]:\n            try:\n                return {read_id: path for read_id in self.get_reader(path).read_ids}\n            except Exception as exc:\n                msg = f\"DatasetReader error reading: {[path]}\"\n                raise Pod5ApiException(msg) from exc\n\n        self._index = {}\n        for index_item in self._run_max_workers(_get_index, self.paths, self.threads):\n            self._index.update(index_item)\n\n    def _issue_duplicate_read_warning(self) -> None:\n        if self.warn_duplicate_indexing:\n            warnings.warn(\"duplicate read_ids found in dataset\")\n\n    @staticmethod\n    def _run_max_workers(\n        fn: Callable[[Any], Any], iterable: Iterable[Any], max_workers: int\n    ) -> Generator[Any, None, None]:\n        assert max_workers > 0\n        futures: Set[Future] = set()\n        with ThreadPoolExecutor(max_workers=max_workers) as executor:\n            for item in iterable:\n                futures.add(executor.submit(fn, item))\n                if len(futures) >= max_workers:\n                    future = next(as_completed(futures))\n                    yield future.result()\n                    futures.remove(future)\n\n            for future in as_completed(futures):\n                yield future.result()\n\n    def __enter__(self) -> \"DatasetReader\":\n        return self\n\n    def __exit__(self, *exc_details) -> None:\n        self.clear_index()\n        self.clear_readers()\n"
  },
  {
    "path": "python/pod5/src/pod5/pod5_types.py",
    "content": "\"\"\"\nContainer class for a pod5 Read object\n\"\"\"\n\nimport datetime\nimport enum\nimport math\nimport os\nfrom dataclasses import dataclass, field\nfrom typing import Dict, List, Union\nfrom uuid import UUID\n\nimport numpy as np\nimport numpy.typing as npt\n\nfrom pod5.signal_tools import vbz_decompress_signal_chunked\n\nPathOrStr = Union[os.PathLike, str]\n\n\nclass EndReasonEnum(enum.Enum):\n    \"\"\"EndReason Enumeration\"\"\"\n\n    UNKNOWN = 0\n    MUX_CHANGE = 1\n    UNBLOCK_MUX_CHANGE = 2\n    DATA_SERVICE_UNBLOCK_MUX_CHANGE = 3\n    SIGNAL_POSITIVE = 4\n    SIGNAL_NEGATIVE = 5\n    API_REQUEST = 6\n    DEVICE_DATA_ERROR = 7\n    ANALYSIS_CONFIG_CHANGE = 8\n    PAUSED = 9\n\n\n# The bool encodes if the reads are \"forced\" to end. Reads are forced to end if it is due to\n# some stimulus outside of the signal such as a device-error or an unblock operation.\n_END_REASON_FORCED_DEFAULTS: Dict[EndReasonEnum, bool] = {\n    EndReasonEnum.UNKNOWN: False,\n    EndReasonEnum.MUX_CHANGE: True,\n    EndReasonEnum.UNBLOCK_MUX_CHANGE: True,\n    EndReasonEnum.DATA_SERVICE_UNBLOCK_MUX_CHANGE: True,\n    EndReasonEnum.SIGNAL_POSITIVE: False,\n    EndReasonEnum.SIGNAL_NEGATIVE: False,\n    EndReasonEnum.API_REQUEST: True,\n    EndReasonEnum.DEVICE_DATA_ERROR: True,\n    EndReasonEnum.ANALYSIS_CONFIG_CHANGE: True,\n    EndReasonEnum.PAUSED: True,\n}\n\n\n@dataclass(frozen=True)\nclass EndReason:\n    \"\"\"\n    Data on why the Read ended.\n\n    Parameters\n    ----------\n\n    reason: EndReasonEnum\n        The end reason enumeration.\n    forced: bool\n        True if it is a 'forced' read break.\n    \"\"\"\n\n    #: The end reason enumeration\n    reason: EndReasonEnum\n    #: True if it is a 'forced' read break (e.g. mux_change, unblock), False otherwise.\n    forced: bool\n\n    @property\n    def name(self) -> str:\n        \"\"\"Return the reason name as a lower string\"\"\"\n        return self.reason.name.lower()\n\n    @classmethod\n    def from_reason_with_default_forced(cls, reason: EndReasonEnum) -> \"EndReason\":\n        \"\"\"\n        Return a new EndReason instance with the 'forced' flag set to the expected\n        default for the given reason\n        \"\"\"\n        return cls(reason=reason, forced=_END_REASON_FORCED_DEFAULTS[reason])\n\n\n@dataclass()\nclass Calibration:\n    \"\"\"\n    Parameters to convert the signal data to picoamps.\n\n    Parameters\n    ----------\n\n    offset: float\n        Calibration offset used to convert raw ADC data into pA readings.\n    scale: float\n        Calibration scale factor used to convert raw ADC data into pA readings.\n    \"\"\"\n\n    #: Calibration offset used to convert raw ADC data into pA readings.\n    offset: float\n    #: Calibration scale factor used to convert raw ADC data into pA readings.\n    scale: float\n\n    @classmethod\n    def from_range(\n        cls, offset: float, adc_range: float, digitisation: float\n    ) -> \"Calibration\":\n        \"\"\"Create a Calibration instance from offset, adc_range and digitisation\"\"\"\n        return cls(offset, adc_range / digitisation)\n\n\n@dataclass()\nclass Pore:\n    \"\"\"\n    Data for the pore that the Read was acquired on\n\n    Parameters\n    ----------\n\n    channel: int\n        1-indexed channel.\n    well: int\n        1-indexed well.\n    pore_type: PoreType\n        The pore type present in the well.\n    \"\"\"\n\n    #: 1-indexed channel.\n    channel: int\n    #: 1-indexed well.\n    well: int\n    #: Name of the pore type present in the well.\n    pore_type: str\n\n\n@dataclass(frozen=True)\nclass RunInfo:\n    \"\"\"\n    Higher-level information about the Reads that correspond to a part of an\n    experiment, protocol or acquisition\n\n    Parameters\n    ----------\n\n    acquisition_id : str\n        A unique identifier for the acquisition.\n    acquisition_start_time : datetime.datetime\n        This is the clock time for sample 0\n    adc_max : int\n        The maximum ADC value that might be encountered.\n    adc_min : int\n        The minimum ADC value that might be encountered.\n    context_tags : Dict[str, str]\n        The context tags for the run. (For compatibility with fast5).\n    experiment_name : str\n        The user-supplied name for the experiment being run.\n    flow_cell_id : str\n        Uniquely identifies the flow cell the data was captured on.\n    flow_cell_product_code : str\n        Identifies the type of flow cell the data was captured on.\n    protocol_name : str\n        The name of the protocol that was run.\n    protocol_run_id : str\n        The unique identifier for the protocol run that produced this data.\n    protocol_start_time : datetime.datetime\n         When the protocol that the acquisition was part of started.\n    sample_id : str\n        A user-supplied name for the sample being analysed.\n    sample_rate : int\n        The number of samples acquired each second on each channel.\n    sequencing_kit : str\n        The type of sequencing kit used to prepare the sample.\n    sequencer_position : str\n        The sequencer position the data was collected on.\n    sequencer_position_type : str\n        The type of sequencing hardware the data was collected on.\n    software : str\n        A description of the software that acquired the data.\n    system_name : str\n        The name of the system the data was collected on.\n    system_type : str\n        The type of system the data was collected on.\n    tracking_id : Dict[str, str]\n        The tracking id for the run. (For compatibility with fast5).\n\n    \"\"\"\n\n    #: A unique identifier for the acquisition - note that readers should not\n    #: depend on this uniquely determining the other fields in the run_info, or being\n    #: unique among the dictionary keys.\n    acquisition_id: str\n    #: This is the clock time for sample 0\n    acquisition_start_time: datetime.datetime\n    #: The maximum ADC value that might be encountered. This is a hardware constraint.\n    adc_max: int\n    #: The minimum ADC value that might be encountered. This is a hardware constraint.\n    adc_min: int\n    #: The context tags for the run. (For compatibility with fast5).\n    context_tags: Dict[str, str] = field(hash=False, compare=True)\n    #: The user-supplied name for the experiment being run.\n    experiment_name: str\n    #: Uniquely identifies the flow cell the data was captured on.\n    #: This is written on the flow cell case.\n    flow_cell_id: str\n    #: Identifies the type of flow cell the data was captured on.\n    flow_cell_product_code: str\n    #: The name of the protocol that was run.\n    protocol_name: str\n    #: The unique identifier for the protocol run that produced this data.\n    protocol_run_id: str\n    #:  When the protocol that the acquisition was part of started.\n    protocol_start_time: datetime.datetime\n    #: A user-supplied name for the sample being analysed.\n    sample_id: str\n    #: The number of samples acquired each second on each channel.\n    sample_rate: int\n    #: The type of sequencing kit used to prepare the sample.\n    sequencing_kit: str\n    #: The sequencer position the data was collected on. For removable positions,\n    #: like MinION Mk1Bs, this is unique (e.g. 'MN12345'), while for integrated\n    #: positions it is not (e.g. 'X1' on a GridION).\n    sequencer_position: str\n    #: The type of sequencing hardware the data was collected on. For example:\n    #: 'MinION Mk1B' or 'GridION' or 'PromethION'.\n    sequencer_position_type: str\n    #: A description of the software that acquired the data. For example:\n    #: 'MinKNOW 21.05.12 (Bream 5.1.6, Configurations 16.2.1, Core 5.1.9, Guppy 4.2.3)'.\n    software: str\n    #: The name of the system the data was collected on. This might be a sequencer\n    #: serial (eg: 'GXB1234') or a host name (e.g. 'Lab PC').\n    system_name: str\n    #: The type of system the data was collected on. For example, 'GridION Mk1' or\n    #: 'PromethION P48'. If the system is not a Nanopore sequencer with built-in\n    #: compute, this will be a description of the operating system\n    #: (e.g. 'Ubuntu 20.04').\n    system_type: str\n    #: The tracking id for the run. (For compatibility with fast5).\n    tracking_id: Dict[str, str] = field(hash=False, compare=True)\n\n\n@dataclass()\nclass ShiftScalePair:\n    \"\"\"A pair of floating point shift and scale values.\"\"\"\n\n    shift: float = field(default=float(\"nan\"))\n    scale: float = field(default=float(\"nan\"))\n\n    def __eq__(self, other: object) -> bool:\n        if not isinstance(other, type(self)):\n            return False\n        if all(\n            math.isnan(x) for x in (self.shift, self.scale, other.shift, other.scale)\n        ):\n            return True\n        return self.shift == other.shift and self.scale == other.scale\n\n\n@dataclass()\nclass BaseRead:\n    \"\"\"\n    Base class for POD5 Read Data\n\n    Parameters\n    ----------\n\n    read_id : UUID\n        The read_id of this read as UUID.\n    pore : Pore\n        Pore data.\n    calibration : Calibration\n        Calibration data.\n    read_number : int\n        The read number on channel. This is increasing but typically\n        not necessarily consecutive.\n    start_sample : int\n        The number samples recorded on this channel before the read started.\n    median_before : float\n        The level of current in the well before this read.\n    end_reason : EndReason\n        EndReason data.\n    run_info : RunInfo\n        RunInfo data.\n    num_minknow_events: int\n        Number of minknow events that the read contains\n    tracked_scaling: ShiftScalePair\n        Shift and Scale for tracked read scaling values (based on previous reads shift)\n    predicted_scaling: ShiftScalePair\n        Shift and Scale for predicted read scaling values (based on this read's raw signal)\n    num_reads_since_mux_change: int\n        Number of selected reads since the last mux change on this reads channel\n    time_since_mux_change: float\n        Time in seconds since the last mux change on this reads channel\n    open_pore_level: float\n        The tracked open pore level for the read.\n    \"\"\"\n\n    #: The read_id of this read as UUID\n    read_id: UUID\n    #: Pore metadata\n    pore: Pore\n    #: Calibration metadata\n    calibration: Calibration\n    #: The read number on channel. This is increasing but typically\n    #: not necessarily consecutive.\n    read_number: int\n    #: The number samples recorded on this channel before the read started.\n    start_sample: int\n    #: The level of current in the well before this read.\n    median_before: float\n    #: EndReason data.\n    end_reason: EndReason\n    #: RunInfo data.\n    run_info: RunInfo\n    #: Number of minknow events that the read contains\n    num_minknow_events: int = field(default=0)\n    #: Shift and Scale for tracked read scaling values (based on previous reads shift)\n    tracked_scaling: ShiftScalePair = field(default_factory=ShiftScalePair)\n    #: Shift and Scale for predicted read scaling values (based on this read's raw signal)\n    predicted_scaling: ShiftScalePair = field(default_factory=ShiftScalePair)\n    #: Number of selected reads since the last mux change on this reads channel\n    num_reads_since_mux_change: int = field(default=0)\n    #: Time in seconds since the last mux change on this reads channel\n    time_since_mux_change: float = field(default=0.0)\n    #: The tracked open pore level for the read.\n    open_pore_level: float = field(default=float(\"nan\"))\n\n\n@dataclass()\nclass Read(BaseRead):\n    \"\"\"\n    POD5 Read Data with an uncompressed signal\n\n    Parameters\n    ----------\n\n    read_id : UUID\n        The read_id of this read as UUID.\n    pore : Pore\n        Pore data.\n    calibration : Calibration\n        Calibration data.\n    read_number : int\n        The read number on channel. This is increasing but typically\n        not necessarily consecutive.\n    start_sample : int\n        The number samples recorded on this channel before the read started.\n    median_before : float\n        The level of current in the well before this read.\n    end_reason : EndReason\n        EndReason data.\n    run_info : RunInfo\n        RunInfo data.\n    signal : numpy.array[int16]\n        Uncompressed signal data.\n    \"\"\"\n\n    #: Uncompressed signal data.\n    signal: npt.NDArray[np.int16] = field(\n        default_factory=lambda: np.array([], dtype=np.int16)\n    )\n\n    @property\n    def sample_count(self) -> int:\n        \"\"\"Return the total number of samples in the uncompressed signal.\"\"\"\n        return len(self.signal)\n\n\n@dataclass()\nclass CompressedRead(BaseRead):\n    \"\"\"\n    POD5 Read Data with a compressed signal.\n\n    Parameters\n    ----------\n\n    read_id : UUID\n        The read_id of this read as UUID.\n    pore : Pore\n        Pore data.\n    calibration : Calibration\n        Calibration data.\n    read_number : int\n        The read number on channel. This is increasing but typically\n        not necessarily consecutive.\n    start_sample : int\n        The number samples recorded on this channel before the read started.\n    median_before : float\n        The level of current in the well before this read.\n    end_reason : EndReason\n        EndReason data.\n    run_info : RunInfo\n        RunInfo data.\n    signal_chunks : List[numpy.array[uint8]]\n        Compressed signal data in chunks.\n    signal_chunk_lengths : List[int]\n        Chunk lengths (number of samples) of signal data **before** compression.\n    \"\"\"\n\n    #: Compressed signal data in chunks.\n    signal_chunks: List[npt.NDArray[np.uint8]] = field(default_factory=list)\n\n    #: Chunk lengths (number of samples) of signal data **before** compression.\n    signal_chunk_lengths: List[int] = field(default_factory=list)\n\n    @property\n    def sample_count(self) -> int:\n        \"\"\"Return the total number of samples in the uncompressed signal.\"\"\"\n        return sum(self.signal_chunk_lengths)\n\n    @property\n    def decompressed_signal(self) -> npt.NDArray[np.int16]:\n        \"\"\"\n        Decompress and return the chunked signal data as a contiguous numpy array.\n\n        Returns\n        -------\n        decompressed_signal : numpy.array[int16]\n            Decompressed signal data\n        \"\"\"\n        return vbz_decompress_signal_chunked(\n            self.signal_chunks, self.signal_chunk_lengths\n        )\n"
  },
  {
    "path": "python/pod5/src/pod5/reader.py",
    "content": "\"\"\"\nTools for accessing POD5 data from PyArrow files\n\"\"\"\n\nimport mmap\nfrom collections import namedtuple\nfrom dataclasses import fields\nfrom deprecated import deprecated\nfrom io import BufferedReader, IOBase\nimport os\nfrom pathlib import Path\nfrom typing import (\n    Collection,\n    Dict,\n    Generator,\n    Iterable,\n    List,\n    Optional,\n    Set,\n    Tuple,\n    Union,\n)\nfrom uuid import UUID\n\nimport lib_pod5 as p5b\nimport numpy as np\nimport numpy.typing as npt\nimport packaging.version\nimport pyarrow as pa\n\nfrom pod5.pod5_types import (\n    Calibration,\n    EndReason,\n    EndReasonEnum,\n    PathOrStr,\n    Pore,\n    Read,\n    RunInfo,\n    ShiftScalePair,\n)\n\nfrom .api_utils import Pod5ApiException, format_read_ids, pack_read_ids, safe_close\nfrom .signal_tools import vbz_decompress_signal, vbz_decompress_signal_into\n\n\nReadRecordV3Columns = namedtuple(\n    \"ReadRecordV3Columns\",\n    [\n        \"read_id\",\n        \"read_number\",\n        \"start\",\n        \"channel\",\n        \"well\",\n        \"median_before\",\n        \"pore_type\",\n        \"calibration_offset\",\n        \"calibration_scale\",\n        \"end_reason\",\n        \"end_reason_forced\",\n        \"run_info\",\n        \"signal\",\n        \"num_minknow_events\",\n        # Deprecated: will be removed in 0.4.0\n        \"tracked_scaling_scale\",\n        # Deprecated: will be removed in 0.4.0\n        \"tracked_scaling_shift\",\n        # Deprecated: will be removed in 0.4.0\n        \"predicted_scaling_scale\",\n        # Deprecated: will be removed in 0.4.0\n        \"predicted_scaling_shift\",\n        # Deprecated: will be removed in 0.4.0\n        \"num_reads_since_mux_change\",\n        # Deprecated: will be removed in 0.4.0\n        \"time_since_mux_change\",\n        \"num_samples\",\n    ],\n)\n\nReadRecordV4Columns = namedtuple(\n    \"ReadRecordV4Columns\",\n    [\n        \"read_id\",\n        \"read_number\",\n        \"start\",\n        \"channel\",\n        \"well\",\n        \"median_before\",\n        \"pore_type\",\n        \"calibration_offset\",\n        \"calibration_scale\",\n        \"end_reason\",\n        \"end_reason_forced\",\n        \"run_info\",\n        \"signal\",\n        \"num_minknow_events\",\n        # Deprecated: will be removed in 0.4.0\n        \"tracked_scaling_scale\",\n        # Deprecated: will be removed in 0.4.0\n        \"tracked_scaling_shift\",\n        # Deprecated: will be removed in 0.4.0\n        \"predicted_scaling_scale\",\n        # Deprecated: will be removed in 0.4.0\n        \"predicted_scaling_shift\",\n        # Deprecated: will be removed in 0.4.0\n        \"num_reads_since_mux_change\",\n        # Deprecated: will be removed in 0.4.0\n        \"time_since_mux_change\",\n        \"num_samples\",\n        \"open_pore_level\",\n    ],\n)\n\n\nSignal = namedtuple(\"Signal\", [\"signal\", \"samples\"])\nSignalRowInfo = namedtuple(\n    \"SignalRowInfo\",\n    [\"batch_index\", \"batch_row_index\", \"sample_count\", \"byte_count\"],\n)\n\n\nclass ReadRecord:\n    \"\"\"\n    Represents the data for a single read from a pod5 record.\n    \"\"\"\n\n    def __init__(\n        self,\n        reader: \"Reader\",\n        batch: \"ReadRecordBatch\",\n        row: int,\n        batch_signal_cache: Optional[List[npt.NDArray[np.int16]]] = None,\n        selected_batch_index: Optional[int] = None,\n    ):\n        \"\"\" \"\"\"\n        self._reader = reader\n        self._batch = batch\n        self._row = row\n        self._batch_signal_cache = batch_signal_cache\n        self._selected_batch_index = selected_batch_index\n\n    @property\n    def read_id(self) -> UUID:\n        \"\"\"\n        Get the unique read identifier for the read as a `UUID`.\n        \"\"\"\n        return UUID(bytes=self._batch.columns.read_id[self._row].as_py())\n\n    @property\n    def read_number(self) -> int:\n        \"\"\"\n        Get the integer read number of the read.\n        \"\"\"\n        return self._batch.columns.read_number[self._row].as_py()  # type: ignore\n\n    @property\n    def start_sample(self) -> int:\n        \"\"\"\n        Get the absolute sample which the read started.\n        \"\"\"\n        return self._batch.columns.start[self._row].as_py()  # type: ignore\n\n    @property\n    def num_samples(self) -> int:\n        \"\"\"\n        Get the number of samples in the reads signal data.\n        \"\"\"\n        return self._batch.columns.num_samples[self._row].as_py()  # type: ignore\n\n    @property\n    def median_before(self) -> float:\n        \"\"\"\n        Get the median before level (in pico amps) for the read.\n        \"\"\"\n        return self._batch.columns.median_before[self._row].as_py()  # type: ignore\n\n    @property\n    def num_minknow_events(self) -> int:\n        \"\"\"\n        Find the number of minknow events in the read.\n        \"\"\"\n        return self._batch.columns.num_minknow_events[self._row].as_py()  # type: ignore\n\n    @property\n    @deprecated(\n        version=\"0.4.0\", reason=\"Scaling fields were unused and will be removed\"\n    )\n    def tracked_scaling(self) -> ShiftScalePair:\n        \"\"\"\n        Find the tracked scaling value in the read.\n        \"\"\"\n        return ShiftScalePair(\n            self._batch.columns.tracked_scaling_shift[self._row].as_py(),\n            self._batch.columns.tracked_scaling_scale[self._row].as_py(),\n        )\n\n    @property\n    @deprecated(\n        version=\"0.4.0\", reason=\"Scaling fields were unused and will be removed\"\n    )\n    def predicted_scaling(self) -> ShiftScalePair:\n        \"\"\"\n        Find the predicted scaling value in the read.\n        \"\"\"\n        return ShiftScalePair(\n            self._batch.columns.predicted_scaling_shift[self._row].as_py(),\n            self._batch.columns.predicted_scaling_scale[self._row].as_py(),\n        )\n\n    @property\n    @deprecated(\n        version=\"0.4.0\", reason=\"Scaling fields were unused and will be removed\"\n    )\n    def num_reads_since_mux_change(self) -> int:\n        \"\"\"\n        Number of selected reads since the last mux change on this reads channel.\n        \"\"\"\n        return self._batch.columns.num_reads_since_mux_change[self._row].as_py()  # type: ignore\n\n    @property\n    @deprecated(\n        version=\"0.4.0\", reason=\"Scaling fields were unused and will be removed\"\n    )\n    def time_since_mux_change(self) -> float:\n        \"\"\"\n        Time in seconds since the last mux change on this reads channel.\n        \"\"\"\n        return self._batch.columns.time_since_mux_change[self._row].as_py()  # type: ignore\n\n    @property\n    def open_pore_level(self) -> float:\n        \"\"\"\n        Get the open pore level for the read.\n\n        This is a float value representing the open pore level of the well prior to the read starting.\n        \"\"\"\n        return self._batch.columns.open_pore_level[self._row].as_py()\n\n    @property\n    def pore(self) -> Pore:\n        \"\"\"\n        Get the pore data associated with the read.\n        \"\"\"\n        return Pore(\n            self._batch.columns.channel[self._row].as_py(),\n            self._batch.columns.well[self._row].as_py(),\n            self._batch.columns.pore_type[self._row].as_py(),\n        )\n\n    @property\n    def calibration(self) -> Calibration:\n        \"\"\"\n        Get the calibration data associated with the read.\n        \"\"\"\n        return Calibration(\n            self._batch.columns.calibration_offset[self._row].as_py(),\n            self._batch.columns.calibration_scale[self._row].as_py(),\n        )\n\n    @property\n    def calibration_digitisation(self) -> int:\n        \"\"\"\n        Get the digitisation value used by the sequencer.\n\n        Intended to assist workflows ported from legacy file formats.\n        \"\"\"\n        return self.run_info.adc_max - self.run_info.adc_min + 1\n\n    @property\n    def calibration_range(self) -> float:\n        \"\"\"\n        Get the calibration range value.\n\n        Intended to assist workflows ported from legacy file formats.\n        \"\"\"\n        return self.calibration.scale * self.calibration_digitisation\n\n    @property\n    def end_reason(self) -> EndReason:\n        \"\"\"\n        Get the end reason data associated with the read.\n        \"\"\"\n        return EndReason(\n            reason=EndReasonEnum[\n                self._batch.columns.end_reason[self._row].as_py().upper()\n            ],\n            forced=self._batch.columns.end_reason_forced[self._row].as_py(),\n        )\n\n    @property\n    def run_info(self) -> RunInfo:\n        \"\"\"\n        Get the run info data associated with the read.\n        \"\"\"\n        return self._reader._lookup_run_info(self._batch, self._row)\n\n    @property\n    def end_reason_index(self) -> int:\n        \"\"\"\n        Get the dictionary index of the end reason data associated with the read.\n        This property is the same as the EndReason enumeration value.\n        \"\"\"\n        return self._batch.columns.end_reason[self._row].index.as_py()  # type: ignore\n\n    @property\n    def run_info_index(self) -> int:\n        \"\"\"\n        Get the dictionary index of the run info data associated with the read.\n        \"\"\"\n        return self._batch.columns.run_info[self._row].index.as_py()  # type: ignore\n\n    @property\n    def sample_count(self) -> int:\n        \"\"\"\n        Get the number of samples in the reads signal data.\n        \"\"\"\n        return self.num_samples\n\n    @property\n    def byte_count(self) -> int:\n        \"\"\"\n        Get the number of bytes used to store the reads data.\n        \"\"\"\n        return sum(r.byte_count for r in self.signal_rows)\n\n    @property\n    def has_cached_signal(self) -> bool:\n        \"\"\"\n        Get if cached signal is available for this read.\n        \"\"\"\n        return self._batch_signal_cache is not None\n\n    @property\n    def signal(self) -> npt.NDArray[np.int16]:\n        \"\"\"\n        Get the full signal for the read.\n\n        Returns\n        -------\n        numpy.ndarray[int16]\n            A numpy array of signal data with int16 type.\n        \"\"\"\n        if self._batch_signal_cache is not None:\n            if self._selected_batch_index is not None:\n                return self._batch_signal_cache[self._selected_batch_index]\n            return self._batch_signal_cache[self._row]\n\n        rows = self._batch.columns.signal[self._row]\n        batch_data = [self._find_signal_row_index(r.as_py()) for r in rows]\n        sample_counts = []\n        for batch, _, batch_row_index in batch_data:\n            sample_counts.append(batch.samples[batch_row_index].as_py())\n\n        output = np.empty(dtype=np.int16, shape=(sum(sample_counts),))\n        current_sample_index = 0\n\n        for i, (batch, _, batch_row_index) in enumerate(batch_data):\n            signal = batch.signal\n            current_row_count = sample_counts[i]\n            output_slice = output[\n                current_sample_index : current_sample_index + current_row_count\n            ]\n            if self._reader.is_vbz_compressed:\n                vbz_decompress_signal_into(\n                    memoryview(signal[batch_row_index].as_buffer()), output_slice\n                )\n            else:\n                output_slice[:] = signal[batch_row_index].values\n            current_sample_index += current_row_count\n        return output\n\n    @property\n    def signal_pa(self) -> npt.NDArray[np.float32]:\n        \"\"\"\n        Get the full signal for the read, calibrated in pico amps.\n\n        Returns\n        -------\n        numpy.ndarray[float32]\n            A numpy array of signal data in pico amps with float32 type.\n        \"\"\"\n        return self.calibrate_signal_array(self.signal)\n\n    def signal_for_chunk(self, index: int) -> npt.NDArray[np.int16]:\n        \"\"\"\n        Get the signal for a given chunk of the read.\n\n        Returns\n        -------\n        numpy.ndarray[int16]\n            A numpy array of signal data with int16 type for the specified chunk.\n        \"\"\"\n        # signal_rows can be used to find details of the signal chunks.\n        chunk_abs_row_index = self._batch.columns.signal[self._row][index]\n        return self._get_signal_for_row(chunk_abs_row_index.as_py())\n\n    @property\n    def signal_rows(self) -> List[SignalRowInfo]:\n        \"\"\"\n        Get all signal rows for the read\n\n        Returns\n        -------\n        list[SignalRowInfo]\n            A list of signal row data (as SignalRowInfo) in the read.\n        \"\"\"\n\n        def map_signal_row(sig_row) -> SignalRowInfo:\n            sig_row = sig_row.as_py()\n\n            batch, batch_index, batch_row_index = self._find_signal_row_index(sig_row)\n            batch_length = 0\n            if isinstance(batch.signal, pa.lib.LargeListArray):\n                batch_length = len(batch.signal[batch_row_index])\n            else:\n                batch_length = len(batch.signal[batch_row_index].as_buffer())\n            return SignalRowInfo(\n                batch_index,\n                batch_row_index,\n                batch.samples[batch_row_index].as_py(),\n                batch_length,\n            )\n\n        return [map_signal_row(r) for r in self._batch.columns.signal[self._row]]\n\n    def calibrate_signal_array(\n        self, signal_array_adc: npt.NDArray[np.int16]\n    ) -> npt.NDArray[np.float32]:\n        \"\"\"\n        Transform an array of int16 signal data from ADC space to pA.\n\n        Returns\n        -------\n        A numpy array of signal data with float32 type.\n        \"\"\"\n        offset = np.float32(self.calibration.offset)\n        scale = np.float32(self.calibration.scale)\n        return (signal_array_adc + offset) * scale\n\n    def _find_signal_row_index(self, signal_row: int) -> Tuple[Signal, int, int]:\n        \"\"\"\n        Map from a signal_row to a Signal, batch index and row index within that batch.\n\n        Returns\n        -------\n        A Tuple containing the `Signal` and its `batch_index` and `row_index`\n        \"\"\"\n        sig_row_count: int = self._reader.signal_batch_row_count\n        sig_batch_idx: int = signal_row // sig_row_count\n        sig_batch = self._reader._get_signal_batch(sig_batch_idx)\n        batch_row_idx: int = signal_row - (sig_batch_idx * sig_row_count)\n\n        return sig_batch, sig_batch_idx, batch_row_idx\n\n    def _get_signal_for_row(self, signal_row: int) -> npt.NDArray[np.int16]:\n        \"\"\"\n        Get the signal data for a given absolute signal row index\n\n        Returns\n        -------\n        A numpy array of signal data with int16 type.\n        \"\"\"\n        batch, _, batch_row_index = self._find_signal_row_index(signal_row)\n\n        signal = batch.signal\n        if self._reader.is_vbz_compressed:\n            sample_count = batch.samples[batch_row_index].as_py()\n            return vbz_decompress_signal(\n                memoryview(signal[batch_row_index].as_buffer()), sample_count\n            )\n\n            return signal.to_numpy()\n        else:\n            return np.array(signal[batch_row_index].values, dtype=\"int16\")\n\n    def to_read(self) -> Read:\n        \"\"\"\n        Create a mutable `Read` from this `ReadRecord` instance.\n\n        Returns\n        -------\n        Read\n        \"\"\"\n        return Read(\n            read_id=self.read_id,\n            pore=self.pore,\n            calibration=self.calibration,\n            median_before=self.median_before,\n            end_reason=self.end_reason,\n            read_number=self.read_number,\n            run_info=self.run_info,\n            start_sample=self.start_sample,\n            num_minknow_events=self.num_minknow_events,\n            tracked_scaling=self.tracked_scaling,\n            predicted_scaling=self.predicted_scaling,\n            num_reads_since_mux_change=self.num_reads_since_mux_change,\n            time_since_mux_change=self.time_since_mux_change,\n            open_pore_level=self.open_pore_level,\n            signal=self.signal,\n        )\n\n\nclass ReadRecordBatch:\n    \"\"\"\n    Read data for a batch of reads.\n    \"\"\"\n\n    def __init__(self, reader: \"Reader\", batch: pa.RecordBatch):\n        \"\"\" \"\"\"\n\n        self._reader: \"Reader\" = reader\n        self._batch: pa.RecordBatch = batch\n\n        self._signal_cache: Optional[p5b.Pod5SignalCacheBatch] = None\n        self._selected_batch_rows: Optional[Iterable[int]] = None\n        self._columns: Optional[ReadRecordV4Columns] = None\n\n    @property\n    def columns(self) -> ReadRecordV4Columns:\n        \"\"\"Return the data from this batch as a ReadRecordColumns instance\"\"\"\n        if self._columns is None:\n            self._columns = ReadRecordV4Columns(\n                *[\n                    self._batch.column(name)\n                    for name in self._reader._columns_type._fields\n                ]\n            )\n        return self._columns\n\n    def set_cached_signal(self, signal_cache: p5b.Pod5SignalCacheBatch) -> None:\n        \"\"\"Set the signal cache\"\"\"\n        self._signal_cache = signal_cache\n\n    def set_selected_batch_rows(self, selected_batch_rows: Iterable[int]) -> None:\n        \"\"\"Set the selected batch rows\"\"\"\n        self._selected_batch_rows = selected_batch_rows\n\n    def reads(self) -> Generator[ReadRecord, None, None]:\n        \"\"\"\n        Iterate all reads in this batch.\n\n        Yields\n        ------\n        ReadRecord\n            ReadRecord instances in the file.\n        \"\"\"\n\n        signal_cache = None\n        if self._signal_cache and self._signal_cache.samples:\n            signal_cache = self._signal_cache.samples\n\n        if self._selected_batch_rows is not None:\n            for idx, row in enumerate(self._selected_batch_rows):\n                yield ReadRecord(\n                    self._reader,\n                    self,\n                    row,\n                    batch_signal_cache=signal_cache,\n                    selected_batch_index=idx,\n                )\n        else:\n            for i in range(self.num_reads):\n                yield ReadRecord(self._reader, self, i, batch_signal_cache=signal_cache)\n\n    def get_read(self, row: int) -> ReadRecord:\n        \"\"\"Get the ReadRecord at row index\"\"\"\n        return ReadRecord(self._reader, self, row)\n\n    @property\n    def num_reads(self) -> int:\n        \"\"\"Return the number of rows in this RecordBatch\"\"\"\n        return int(self._batch.num_rows)\n\n    @property\n    def read_id_column(self) -> pa.FixedSizeBinaryArray:\n        \"\"\"\n        Get the column of read ids for this batch\n        \"\"\"\n        if self._selected_batch_rows is not None:\n            return self.columns.read_id.take(self._selected_batch_rows)\n        return self.columns.read_id\n\n    @property\n    def read_number_column(self) -> pa.UInt32Array:\n        \"\"\"\n        Get the column of read numbers for this batch\n        \"\"\"\n        if self._selected_batch_rows is not None:\n            return self.columns.read_number.take(self._selected_batch_rows)\n        return self.columns.read_number\n\n    @property\n    def cached_sample_count_column(self) -> npt.NDArray[np.uint64]:\n        \"\"\"\n        Get the sample counts from the cached signal data\n        \"\"\"\n        if not self._signal_cache:\n            raise RuntimeError(\"No cached signal data available\")\n        return self._signal_cache.sample_count\n\n    @property\n    def cached_samples_column(self) -> List[npt.NDArray[np.int16]]:\n        \"\"\"\n        Get the samples column from the cached signal data\n        \"\"\"\n        if not self._signal_cache:\n            raise RuntimeError(\"No cached signal data available\")\n        return self._signal_cache.samples\n\n\nclass ArrowTableHandle:\n    \"\"\"Class for managing arrow file handles and memory view mapping of tables\"\"\"\n\n    def __init__(\n        self,\n        location: p5b.EmbeddedFileData,\n        options: Optional[pa.ipc.IpcReadOptions] = None,\n    ) -> None:\n        \"\"\"\n        Open a pod5 file at the given `path` and use the location data to load\n        an arrow table (e.g. signal table)\n\n        Parameters\n        ----------\n        location : lib_pod5.pod5_format_pybind.EmbeddedFileData\n            Location data for how a pod5 file should be spit in memory to read a table.\n            This is returned from p5b.Pod5FileReader.get_file_X_location methods\n        options: pa.ipc.IpcReadOptions\n            Serialization options for reading IPC format.\n\n        Raises\n        ------\n        Pod5ApiException\n            If handle could not be opened\n        \"\"\"\n\n        # The location data is passed from the p5b.Pod5FileReader.get_file_X_location\n        # methods\n        self._location = location\n        self._options = options\n        self._path = Path(self._location.file_path)\n\n        self._fh: Union[BufferedReader, None] = None\n        self._mmap: Union[mmap.mmap, None] = None\n        self._reader: Union[pa.RecordBatchFileReader, None] = None\n        self._stream: Union[pa.PythonFile, pa.NativeFile, None] = None\n\n        self._fh = None\n        if \"POD5_DISABLE_MMAP_OPEN\" in os.environ:\n            self._stream = self._open_without_mmap()\n        else:\n            # Create a memory view of the file and select the region for the table\n            try:\n                self._stream = self._open_with_mmap()\n            except OSError:\n                # If we fail fall back to a traditional open.\n                self._stream = self._open_without_mmap()\n\n        self._reader = pa.ipc.open_file(self._stream, options=self._options)\n\n    def _open_without_mmap(self) -> pa.PythonFile:\n        class File(IOBase):\n            def __init__(self, handle, location):\n                self._handle = handle\n                self._location = location\n                self.seek(0, whence=0)\n\n            def seek(self, position, whence=0):\n                if whence == 0:\n                    position = position + self._location.offset\n                elif whence == 2:\n                    position = (\n                        self._location.offset + self._location.length\n                    ) - position\n                    whence = 0\n                # The new abs location:\n                abs_location = self._handle.seek(position, whence)\n\n                return abs_location - self._location.offset\n\n            def read(self, size=-1):\n                return self._handle.read(size)\n\n        if self._fh is None:\n            self._fh = self._path.open(\"rb\")\n\n        return pa.PythonFile(File(self._fh, self._location))\n\n    def _open_with_mmap(self) -> pa.BufferReader:\n        loc = self._location\n        # Get the page-aligned offset of this table.\n        # If the inner file doesn't align to a page, get the offset to the\n        # previous page and extend the length accordingly.\n        alignment_remainder = loc.offset % mmap.ALLOCATIONGRANULARITY\n        aligned_offset = loc.offset - alignment_remainder\n        aligned_length = loc.length + alignment_remainder\n\n        # Temporarily open file to reduce open file handles\n        with self._path.open(\"rb\") as fh:\n            self._mmap = mmap.mmap(\n                fh.fileno(),\n                offset=aligned_offset,\n                length=aligned_length,\n                access=mmap.ACCESS_READ,\n            )\n            # Slice to remove any leading bytes which are not in the table\n            # added from page-alignment\n            arrow_table_view = memoryview(self._mmap)[alignment_remainder:]\n\n        try:\n            return pa.BufferReader(arrow_table_view)\n        except pa.ArrowInvalid as exc:\n            raise Pod5ApiException(f\"Failed to open ArrowTable: {self._path}\") from exc\n\n    @property\n    def reader(self) -> pa.ipc.RecordBatchFileReader:\n        \"\"\"Return the pyarrow file reader object\"\"\"\n        if self._reader is not None:\n            return self._reader\n\n        raise RuntimeError(f\"Could not open pyarrow reader: {p5b.get_error_string()}\")\n\n    @property\n    def stream(self) -> Union[pa.PythonFile, pa.NativeFile]:\n        \"\"\"Return the pyarrow file stream / backend\"\"\"\n        if self._stream is not None:\n            return self._stream\n\n        raise RuntimeError(f\"Could not open pyarrow stream: {p5b.get_error_string()}\")\n\n    def close(self) -> None:\n        \"\"\"\n        Cleanly close the open file handles and memory views.\n        \"\"\"\n        safe_close(self, \"_reader\")\n        self._reader = None\n\n        safe_close(self, \"_stream\")\n        self._stream = None\n\n        safe_close(self, \"_mmap\")\n        self._mmap = None\n\n        safe_close(self, \"_fh\")\n        self._fh = None\n\n    def __enter__(self) -> \"ArrowTableHandle\":\n        return self\n\n    def __exit__(self, *exc_details) -> None:\n        self.close()\n\n    def __del__(self):\n        self.close()\n\n\nclass Reader:\n    \"\"\"\n    The base reader for POD5 data\n    \"\"\"\n\n    def __init__(self, path: PathOrStr):\n        \"\"\"\n        Open a pod5 filepath for reading\n        \"\"\"\n        self._path = Path(path).absolute()\n\n        self._file_reader: Optional[p5b.Pod5FileReader] = None\n        self._read_handle: Optional[ArrowTableHandle] = None\n        self._run_info_handle: Optional[ArrowTableHandle] = None\n        self._signal_handle: Optional[ArrowTableHandle] = None\n\n        (\n            self._file_reader,\n            self._read_handle,\n            self._run_info_handle,\n            self._signal_handle,\n        ) = self._open_arrow_table_handles(self._path)\n\n        schema_metadata = self.read_table.schema.metadata\n        self._file_identifier = UUID(\n            schema_metadata[b\"MINKNOW:file_identifier\"].decode(\"utf-8\")\n        )\n        self._writing_software = schema_metadata[b\"MINKNOW:software\"].decode(\"utf-8\")\n        writing_version_str = schema_metadata[b\"MINKNOW:pod5_version\"].decode(\"utf-8\")\n        writing_version = packaging.version.parse(writing_version_str)\n\n        self._columns_type = ReadRecordV4Columns\n        self._reads_table_version = 4\n\n        self._file_version = writing_version\n        self._file_version_pre_migration = packaging.version.Version(\n            self._file_reader.get_file_version_pre_migration()\n        )\n\n        # Warning: The cached signal maintains an open file handle. So ensure that\n        # this dictionary is cleared before closing.\n        self._cached_signal_batches: Dict[int, Signal] = {}\n        self._cached_run_infos: Dict[str, RunInfo] = {}\n\n        self._is_vbz_compressed: Optional[bool] = None\n        self._signal_batch_row_count: Optional[int] = None\n\n    @staticmethod\n    def _open_arrow_table_handles(\n        path: Path,\n    ) -> Tuple[\n        p5b.Pod5FileReader, ArrowTableHandle, ArrowTableHandle, ArrowTableHandle\n    ]:\n        \"\"\"Open handles to the underlying arrow tables within this pod5 file\"\"\"\n        if not path.is_file():\n            raise FileNotFoundError(f\"Failed to open pod5 file at: {path}\")\n\n        file_reader = p5b.open_file(str(path))\n        if not file_reader:\n            raise Pod5ApiException(\n                f\"Failed to open reader for {path} Reason: {p5b.get_error_string()}\"\n            )\n\n        read_handle = ArrowTableHandle(file_reader.get_file_read_table_location())\n        run_info_handle = ArrowTableHandle(\n            file_reader.get_file_run_info_table_location()\n        )\n        signal_handle = ArrowTableHandle(file_reader.get_file_signal_table_location())\n        return file_reader, read_handle, run_info_handle, signal_handle\n\n    def __enter__(self) -> \"Reader\":\n        return self\n\n    def __exit__(self, *exc_details) -> None:\n        self.close()\n\n    def __iter__(self) -> Generator[ReadRecord, None, None]:\n        \"\"\"Iterate over all reads\"\"\"\n        yield from self.reads()\n\n    def close(self) -> None:\n        \"\"\"Close files handles\"\"\"\n\n        safe_close(self, \"_read_handle\")\n        self._read_handle = None\n\n        safe_close(self, \"_run_info_handle\")\n        self._run_info_handle = None\n\n        safe_close(self, \"_signal_handle\")\n        self._signal_handle = None\n\n        safe_close(self, \"_file_reader\")\n        self._file_reader = None\n\n        # Explicitly clear this dictionary to close file handles used in cache\n        self._cached_signal_batches = {}\n\n    @property\n    def path(self) -> Path:\n        \"\"\"Return the path to this pod5 file\"\"\"\n        return self._path\n\n    @property\n    def inner_file_reader(self) -> p5b.Pod5FileReader:\n        \"\"\"Access the inner c_api Pod5FileReader - use with caution\"\"\"\n        if self._file_reader is None:\n            raise RuntimeError(\"Pod5FileReader has been closed!\")\n        return self._file_reader\n\n    @property\n    def read_table(self) -> pa.ipc.RecordBatchFileReader:\n        \"\"\"Access the pod5 read table\"\"\"\n        if self._read_handle is None:\n            raise RuntimeError(\"ArrowTableHandle has been closed!\")\n        return self._read_handle.reader\n\n    @property\n    def run_info_table(self) -> pa.ipc.RecordBatchFileReader:\n        \"\"\"Access the pod5 run_info table\"\"\"\n        if self._run_info_handle is None:\n            raise RuntimeError(\"ArrowTableHandle has been closed!\")\n        return self._run_info_handle.reader\n\n    @property\n    def signal_table(self) -> pa.ipc.RecordBatchFileReader:\n        \"\"\"Access the pod5 signal table - use with caution\"\"\"\n        if self._signal_handle is None:\n            raise RuntimeError(\"ArrowTableHandle has been closed!\")\n        return self._signal_handle.reader\n\n    @property\n    def file_version(self) -> packaging.version.Version:\n        \"\"\"The version of pod5 that originally generated this file, this is not updated when updating the file.\"\"\"\n        return self._file_version\n\n    @property\n    def file_version_pre_migration(self) -> packaging.version.Version:\n        \"\"\"The version of pod5 that is stored with the file on disk.\"\"\"\n        return self._file_version_pre_migration\n\n    @property\n    def writing_software(self) -> str:\n        return self._writing_software\n\n    @property\n    def file_identifier(self) -> UUID:\n        return self._file_identifier\n\n    @property\n    def reads_table_version(self) -> int:\n        return self._reads_table_version\n\n    @property\n    def is_vbz_compressed(self) -> bool:\n        \"\"\"Return if this file's signal is compressed\"\"\"\n        if self._is_vbz_compressed is None:\n            self._is_vbz_compressed = self.signal_table.schema.field(\n                \"signal\"\n            ).type.equals(pa.large_binary())\n        return self._is_vbz_compressed\n\n    @property\n    def signal_batch_row_count(self) -> int:\n        \"\"\"Return signal batch row count\"\"\"\n        if self._signal_batch_row_count is None:\n            if self.signal_table.num_record_batches > 0:\n                self._signal_batch_row_count = self.signal_table.get_batch(0).num_rows\n            else:\n                self._signal_batch_row_count = 0\n        return self._signal_batch_row_count\n\n    @property\n    def batch_count(self) -> int:\n        \"\"\"\n        Find the number of read batches available in the file.\n        \"\"\"\n        return self.read_table.num_record_batches\n\n    @property\n    def num_reads(self) -> int:\n        \"\"\"\n        Find the number of reads in the file.\n        \"\"\"\n        # We write constant size batches except for the last.\n        num_batches = self.read_table.num_record_batches\n        return (\n            self.read_table.get_batch(0).num_rows * max(num_batches - 1, 0)\n            + self.read_table.get_batch(num_batches - 1).num_rows\n        )\n\n    @property\n    def read_ids_raw(self) -> pa.ChunkedArray:\n        \"\"\"\n        Return chunked arrow array of read ids.\n\n        To get read ids as string use `Reader.read_ids`\n        \"\"\"\n\n        return pa.chunked_array([batch.read_id_column for batch in self.read_batches()])\n\n    @property\n    def read_ids(self) -> List[str]:\n        \"\"\"\n        Return all read_ids as a list of strings.\n\n        For the most performant implementation consider `Reader.read_ids_raw`\n        \"\"\"\n\n        def arrow_to_numpy(batch):\n            # Get the arrow data as a buffer\n            id_buffer = batch.read_id_column.buffers()[1]\n\n            # Pack the arrow buffer into a numpy array of the the right shape\n            array = np.frombuffer(id_buffer, dtype=np.uint8)\n            return array.reshape((batch.num_reads, 16))\n\n        read_ids = np.concatenate(\n            [arrow_to_numpy(batch) for batch in self.read_batches()]\n        )\n        return format_read_ids(read_ids)\n\n    def get_batch(self, index: int) -> ReadRecordBatch:\n        \"\"\"\n        Get a read batch in the file.\n\n        Returns\n        -------\n        ReadRecordBatch\n            The requested batch as a ReadRecordBatch.\n        \"\"\"\n        return ReadRecordBatch(self, self.read_table.get_batch(index))\n\n    def read_batches(\n        self,\n        selection: Optional[List[str]] = None,\n        batch_selection: Optional[Iterable[int]] = None,\n        missing_ok: bool = False,\n        preload: Optional[Set[str]] = None,\n    ) -> Generator[ReadRecordBatch, None, None]:\n        \"\"\"\n        Iterate batches in the file, optionally selecting certain rows.\n\n        Parameters\n        ----------\n        selection : iterable[str]\n            The read ids to walk in the file.\n        batch_selection : iterable[int]\n            The read batches to walk in the file.\n        missing_ok : bool\n            If selection contains entries not found in the file, an error will be raised.\n        preload : set[str]\n            Columns to preload - \"samples\" and \"sample_count\" are valid values\n\n        Returns\n        -------\n        Generator[ReadRecordBatch, None, None]\n            A generator yielding `ReadRecordBatch`s\n        \"\"\"\n        if selection is not None:\n            if batch_selection is not None:\n                raise ValueError(\"selection and batch_selection are mutually exclusive\")\n            yield from self._select_read_batches(\n                selection, missing_ok=missing_ok, preload=preload\n            )\n        elif batch_selection is not None:\n            assert not selection\n            yield from self._read_some_batches(batch_selection, preload=preload)\n        else:\n            yield from self._reads_batches(preload=preload)\n\n    def reads(\n        self,\n        selection: Optional[Iterable[str]] = None,\n        missing_ok: bool = False,\n        preload: Optional[Set[str]] = None,\n    ) -> Generator[ReadRecord, None, None]:\n        \"\"\"\n        Iterate reads in the file, optionally filtering for certain read ids.\n\n        Parameters\n        ----------\n        selection : iterable[str]\n            The read ids to walk in the file.\n        missing_ok : bool\n            If selection contains entries not found in the file, an error will be raised.\n        preload : set[str]\n            Columns to preload - \"samples\" and \"sample_count\" are valid values\n\n        Returns\n        -------\n        Generator[ReadRecord, None, None]\n            A generator yielding `ReadRecord`s\n        \"\"\"\n        if selection is None:\n            yield from self._reads(preload=preload)\n        else:\n            yield from self._select_reads(\n                list(selection), missing_ok=missing_ok, preload=preload\n            )\n\n    def _reads(\n        self, preload: Optional[Set[str]] = None\n    ) -> Generator[ReadRecord, None, None]:\n        \"\"\"Generate all reads\"\"\"\n        for batch in self.read_batches(preload=preload):\n            for read in batch.reads():\n                yield read\n\n    def _select_reads(\n        self,\n        selection: List[str],\n        missing_ok: bool = False,\n        preload: Optional[Set[str]] = None,\n    ) -> Generator[ReadRecord, None, None]:\n        \"\"\"Generate selected reads\"\"\"\n        for batch in self._select_read_batches(selection, missing_ok, preload=preload):\n            for read in batch.reads():\n                yield read\n\n    def _reads_batches(\n        self, preload: Optional[Set[str]] = None\n    ) -> Generator[ReadRecordBatch, None, None]:\n        \"\"\"Generate the record batches\"\"\"\n        signal_cache = None\n        if preload:\n            signal_cache = self.inner_file_reader.batch_get_signal(\n                \"samples\" in preload,\n                \"sample_count\" in preload,\n            )\n\n        for idx in range(self.read_table.num_record_batches):\n            batch = self.get_batch(idx)\n            if signal_cache:\n                batch.set_cached_signal(signal_cache.release_next_batch())\n            yield batch\n\n    def _read_some_batches(\n        self,\n        batch_selection: Iterable[int],\n        preload: Optional[Set[str]] = None,\n    ) -> Generator[ReadRecordBatch, None, None]:\n        \"\"\"Generate the selected record batches\"\"\"\n        signal_cache = None\n        if preload:\n            signal_cache = self.inner_file_reader.batch_get_signal_batches(\n                \"samples\" in preload,\n                \"sample_count\" in preload,\n                np.array(batch_selection, dtype=np.uint32),\n            )\n\n        for i in batch_selection:\n            batch = self.get_batch(i)\n            if signal_cache:\n                batch.set_cached_signal(signal_cache.release_next_batch())\n            yield batch\n\n    def _select_read_batches(\n        self,\n        selection: List[str],\n        missing_ok: bool = False,\n        preload: Optional[Set[str]] = None,\n    ) -> Generator[ReadRecordBatch, None, None]:\n        \"\"\"Generate the selected record batches\"\"\"\n        successful_finds, per_batch_counts, batch_rows = self._plan_traversal(\n            selection, missing_ok=missing_ok\n        )\n\n        if not missing_ok and successful_finds != len(selection):\n            raise RuntimeError(\n                f\"Failed to find {len(selection) - successful_finds} requested reads in the file\"\n            )\n\n        signal_cache: Optional[p5b.Pod5AsyncSignalLoader] = None\n        if preload:\n            signal_cache = self.inner_file_reader.batch_get_signal_selection(\n                \"samples\" in preload,\n                \"sample_count\" in preload,\n                per_batch_counts,\n                batch_rows,\n            )\n\n        current_offset = 0\n        for batch_idx, batch_count in enumerate(per_batch_counts):\n            current_batch_rows = batch_rows[\n                current_offset : current_offset + batch_count\n            ]\n            current_offset += batch_count\n\n            batch = self.get_batch(batch_idx)\n            batch.set_selected_batch_rows(current_batch_rows)\n            if signal_cache:\n                batch.set_cached_signal(signal_cache.release_next_batch())\n            yield batch\n\n    def _plan_traversal(\n        self,\n        read_ids: Union[Collection[str], npt.NDArray[np.uint8]],\n        missing_ok: bool = False,\n    ) -> Tuple[int, npt.NDArray[np.uint32], npt.NDArray[np.uint32]]:\n        \"\"\"\n        Query the file reader indexes to return the number of read_ids which\n        were found and the batches and rows which are needed to traverse each\n        read in the selection.\n\n        Parameters\n        ----------\n        read_ids : Collection or numpy.ndarray of read_id strings\n            The read ids to find in the file\n\n        Returns\n        -------\n        successful_find_count: int\n            The number of reads that were found from the array of read_ids given\n        per_batch_counts: numpy.array[uint32]\n            The number of rows from the batch row ids to take to form each RecordBatch\n        batch_rows: numpy.array[uint32]\n            All batch row ids\n\n        \"\"\"\n        if not isinstance(read_ids, np.ndarray):\n            read_ids = pack_read_ids(read_ids, invalid_ok=missing_ok)\n\n        assert isinstance(read_ids, np.ndarray)\n\n        batch_rows = np.empty(dtype=\"u4\", shape=read_ids.shape[0])\n        per_batch_counts = np.empty(dtype=\"u4\", shape=self.batch_count)\n\n        successful_find_count = self.inner_file_reader.plan_traversal(\n            read_ids,\n            per_batch_counts,\n            batch_rows,\n        )\n\n        return successful_find_count, per_batch_counts, batch_rows\n\n    def _get_signal_batch(self, batch_id: int) -> Signal:\n        \"\"\"Get the `Signal` from the signal_reader batch at batch_id\"\"\"\n        if batch_id in self._cached_signal_batches:\n            return self._cached_signal_batches[batch_id]\n\n        batch = self.signal_table.get_batch(batch_id)\n\n        signal_batch = Signal(*[batch.column(name) for name in Signal._fields])\n\n        self._cached_signal_batches[batch_id] = signal_batch\n        return signal_batch\n\n    def _lookup_run_info(self, batch: ReadRecordBatch, batch_row_id: int) -> RunInfo:\n        \"\"\"Get the `RunInfo` from the batch at `batch_row_id`\"\"\"\n\n        acquisition_id = batch.columns.run_info[batch_row_id].as_py()\n\n        if acquisition_id in self._cached_run_infos:\n            return self._cached_run_infos[acquisition_id]\n\n        run_info = None\n        for idx in range(self.run_info_table.num_record_batches):\n            run_info_batch = self.run_info_table.get_batch(idx)\n            acquisition_id_col = run_info_batch.column(\"acquisition_id\")\n            for row in range(run_info_batch.num_rows):\n                if acquisition_id_col[row].as_py() == acquisition_id:\n                    values = {}\n                    for field in fields(RunInfo):\n                        col = run_info_batch.column(field.name)\n                        values[field.name] = col[row].as_py()\n\n                        if field.name in (\"tracking_id\", \"context_tags\"):\n                            values[field.name] = {k: v for k, v in values[field.name]}\n\n                    run_info = RunInfo(**values)\n                    break\n\n        if not run_info:\n            raise Exception(\n                f\"Failed to find run info '{acquisition_id}' in run info table\"\n            )\n\n        self._cached_run_infos[acquisition_id] = run_info\n        return run_info\n\n    def __del__(self):\n        self.close()\n"
  },
  {
    "path": "python/pod5/src/pod5/repack.py",
    "content": "\"\"\"\nTools to assist repacking pod5 data into other pod5 files\n\"\"\"\n\nfrom typing import Collection\nimport lib_pod5 as p5b\n\nimport pod5 as p5\n\n\nclass Repacker:\n    \"\"\"Wrapper class around native pod5 tools to repack data\"\"\"\n\n    def __init__(self):\n        self._repacker = p5b.Repacker()\n        self._reads_requested = 0\n\n    @property\n    def is_complete(self) -> bool:\n        \"\"\"Find if the requested repack operations are complete\"\"\"\n        return self._repacker.is_complete\n\n    @property\n    def currently_open_file_reader_count(self) -> int:\n        \"\"\"Returns the number of open file readers held by this repacker\"\"\"\n        return self._repacker.currently_open_file_reader_count\n\n    @property\n    def reads_completed(self) -> int:\n        \"\"\"Find the number of reads written to files\"\"\"\n        return self._repacker.reads_completed\n\n    @property\n    def reads_requested(self) -> int:\n        \"\"\"Find the number of requested reads to be written\"\"\"\n        return self._reads_requested\n\n    def add_output(\n        self, output_file: p5.Writer, check_duplicate_read_ids: bool = True\n    ) -> p5b.Pod5RepackerOutput:\n        \"\"\"\n        Add an output file writer to the repacker, so it can have read data repacked\n        into it.\n\n        Once a user has added an output, it can be passed as an output\n        to `add_selected_reads_to_output` or `add_reads_to_output`\n\n        Parameters\n        ----------\n        output_file: Writer\n            The output file writer to use\n        check_duplicate_read_ids: bool\n            Check the output for duplicate read ids, and raise an error if found.\n\n        Returns\n        -------\n        repacker_object: p5b.Pod5RepackerOutput\n            Use this as `output_ref` in calls to `add_selected_reads_to_output`\n            or `add_reads_to_output`\n        \"\"\"\n        assert output_file._writer is not None\n        return self._repacker.add_output(output_file._writer, check_duplicate_read_ids)\n\n    def add_selected_reads_to_output(\n        self,\n        output_ref: p5b.Pod5RepackerOutput,\n        reader: p5.Reader,\n        selected_read_ids: Collection[str],\n    ):\n        \"\"\"\n        Copy the selected read_ids from the given `Reader` into the\n        Repacker output reference which was returned by `add_output`\n\n        Parameters\n        ----------\n        output_ref : lib_pod5.pod5_format_pybind.Pod5RepackerOutput\n            The repacker handle reference returned from `add_output`\n        reader : Reader\n            The Pod5 file reader to copy reads from\n        selected_read_ids: Collection[str]\n            A Collection of read_ids as strings\n\n        Raises\n        ------\n        RuntimeError\n            If any of the selected_read_ids were not found in the source file\n        \"\"\"\n\n        successful_finds, per_batch_counts, all_batch_rows = reader._plan_traversal(\n            selected_read_ids\n        )\n\n        if successful_finds != len(selected_read_ids):\n            raise RuntimeError(\n                f\"Failed to find {len(selected_read_ids) - successful_finds} \"\n                \"requested reads in the source file\"\n            )\n\n        self._reads_requested += successful_finds\n        self._repacker.add_selected_reads_to_output(\n            output_ref, reader.inner_file_reader, per_batch_counts, all_batch_rows\n        )\n\n    def add_all_reads_to_output(\n        self, output_ref: p5b.Pod5RepackerOutput, reader: p5.Reader\n    ) -> None:\n        \"\"\"\n        Copy the every read from the given `Reader` into the\n        Repacker output reference which was returned by `add_output`\n\n        Parameters\n        ----------\n        output_ref : lib_pod5.pod5_format_pybind.Pod5RepackerOutput\n            The repacker handle reference returned from `add_output`\n        reader : Reader\n            The Pod5 file reader to copy reads from\n        \"\"\"\n        self._reads_requested += reader.num_reads\n        self._repacker.add_all_reads_to_output(output_ref, reader.inner_file_reader)\n\n    def finish(self) -> None:\n        \"\"\"\n        Call finish on the underlying c_api repacker instance to write the footer\n        completing the file and freeing resources\n        \"\"\"\n        return self._repacker.finish()\n\n    def set_output_finished(self, output) -> None:\n        \"\"\"\n        Tell the repacker a specific output is complete and can be finalised.\n        \"\"\"\n        return self._repacker.set_output_finished(output)\n"
  },
  {
    "path": "python/pod5/src/pod5/signal_tools.py",
    "content": "\"\"\"\nTools for handling pod5 signals\n\"\"\"\n\nfrom typing import List, Tuple, Union\n\nimport lib_pod5 as p5b\nimport numpy as np\nimport numpy.typing as npt\n\nDEFAULT_SIGNAL_CHUNK_SIZE = 102400\n\n\ndef vbz_decompress_signal(\n    compressed_signal: Union[npt.NDArray[np.uint8], memoryview], sample_count: int\n) -> npt.NDArray[np.int16]:\n    \"\"\"\n    Decompress a contiguous (not-chunked) numpy array of compressed signal data\n\n    Parameters\n    ----------\n    compressed_signal : numpy.ndarray[uint8]\n        The array of compressed signal data to decompress.\n    sample_count : int\n        The number of samples in the original signal\n\n    Returns\n    -------\n    A decompressed signal array numpy.ndarray[int16]\n    \"\"\"\n    if len(compressed_signal) == 0:\n        return np.array([], dtype=np.int16)\n\n    signal = np.empty(sample_count, dtype=\"i2\")\n    p5b.decompress_signal(compressed_signal, signal)\n    return signal\n\n\ndef vbz_decompress_signal_chunked(\n    compressed_signal_chunks: List[npt.NDArray[np.uint8]], sample_counts: List[int]\n) -> npt.NDArray[np.int16]:\n    \"\"\"\n    Decompress a chunks of numpy array of compressed signal data\n\n    Parameters\n    ----------\n    compressed_signal_chunks : List[numpy.ndarray[uint8]]\n        A list of compressed signal data chunks to decompress.\n    sample_counts : List[int]\n        The number of samples in the original signal chunks\n\n    Returns\n    -------\n    A decompressed signal array numpy.ndarray[int16]\n\n    Raises\n    ------\n    ValueError\n        Inconsistent parameter lengths\n    \"\"\"\n    if len(compressed_signal_chunks) != len(sample_counts):\n        raise ValueError(\n            f\"Inconsistent number of chunks to decompress - \"\n            f\"signals: {len(compressed_signal_chunks)}, counts: {len(sample_counts)}\"\n        )\n\n    if len(compressed_signal_chunks) == 0:\n        return np.array([], dtype=np.int16)\n\n    decompressed_signal: npt.NDArray[np.int16] = (\n        np.concatenate(  # type:ignore [no-untyped-call]\n            [\n                vbz_decompress_signal(signal_chunk, sample_count)\n                for signal_chunk, sample_count in zip(\n                    compressed_signal_chunks, sample_counts\n                )\n            ]\n        )\n    )\n    return decompressed_signal\n\n\ndef vbz_decompress_signal_into(\n    compressed_signal: Union[npt.NDArray[np.uint8], memoryview],\n    output_array: npt.NDArray[np.int16],\n) -> npt.NDArray[np.int16]:\n    \"\"\"\n    Decompress a numpy array of compressed signal data into the destination\n    \"output_array\"\n\n    Parameters\n    ----------\n    compressed_signal : numpy.ndarray[uint8]\n        The array of compressed signal data to decompress.\n    output_array : numpy.ndarray[int16]\n        The destination location for signal\n\n    Returns\n    -------\n    A decompressed signal array numpy.ndarray[int16]\n    \"\"\"\n    if len(compressed_signal) == 0:\n        return np.array([], dtype=np.int16)\n\n    p5b.decompress_signal(compressed_signal, output_array)\n    return output_array\n\n\ndef vbz_compress_signal(signal: npt.NDArray[np.int16]) -> npt.NDArray[np.uint8]:\n    \"\"\"\n    Compress a numpy array of signal data\n\n    Parameters\n    ----------\n    signal : numpy.ndarray[int16]\n        The array of signal data to compress.\n\n    Returns\n    -------\n    compressed_signal : numpy.array[uint8]\n        The compressed signal data as a numpy.ndarray[uint8] (byte array)\n    \"\"\"\n    if signal.size == 0:\n        return np.array([], dtype=np.uint8)\n\n    max_signal_size = p5b.vbz_compressed_signal_max_size(len(signal))\n    compressed_signal = np.zeros(max_signal_size, dtype=\"u1\")\n\n    size = p5b.compress_signal(signal, compressed_signal)\n\n    return np.resize(compressed_signal, size)\n\n\ndef vbz_compress_signal_chunked(\n    signal: npt.NDArray[np.int16], signal_chunk_size: int = DEFAULT_SIGNAL_CHUNK_SIZE\n) -> Tuple[List[npt.NDArray[np.uint8]], List[int]]:\n    \"\"\"\n    Compress a numpy array of signal data into chunks\n\n    Parameters\n    ----------\n    signal : numpy.ndarray[int16]\n        The array of signal data to compress.\n    signal_chunk_size : int\n        The number of signal samples in a chunk\n\n    Returns\n    -------\n    compressed_signal_chunks : List[numpy.array[uint8]]\n        A List of chunks of compressed signal data as numpy.ndarray[uint8] (byte arrays)\n    signal_chunk_lengths : List[int]\n        The number of uncompressed signal samples in each chunk\n    \"\"\"\n    signal_chunks: List[npt.NDArray[np.uint8]] = []\n    signal_chunk_lengths: List[int] = []\n\n    # Take slice views of the signal ndarray (non-copying)\n    for slice_index in range(0, len(signal), signal_chunk_size):\n        signal_slice = signal[slice_index : slice_index + signal_chunk_size]\n        signal_chunks.append(vbz_compress_signal(signal_slice))\n        signal_chunk_lengths.append(len(signal_slice))\n\n    return signal_chunks, signal_chunk_lengths\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/__init__.py",
    "content": "\"\"\"POD5 Format Tools\"\"\"\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/main.py",
    "content": "\"\"\"Main entry point for pod5 tools\"\"\"\n\nimport argparse\nimport sys\nfrom typing import Any\n\nfrom pod5 import __version__\nfrom pod5.tools.parsers import (\n    SubcommandHelpFormatter,\n    prepare_pod5_convert,\n    prepare_pod5_filter_argparser,\n    prepare_pod5_inspect_argparser,\n    prepare_pod5_merge_argparser,\n    prepare_pod5_recover_argparser,\n    prepare_pod5_repack_argparser,\n    prepare_pod5_subset_argparser,\n    prepare_pod5_update_argparser,\n    prepare_pod5_view_argparser,\n    run_tool,\n)\n\n\ndef main() -> Any:\n    \"\"\"\n    The core pod5 tools function which assembles the argparser and executes the required\n    pod5 tool.\n    \"\"\"\n    desc = (\n        \"**********      POD5 Tools      **********\\n\\n\"\n        \"Tools for inspecting, converting, subsetting and formatting POD5 files\"\n    )\n\n    parser = argparse.ArgumentParser(\n        prog=\"pod5\",\n        description=desc,\n        epilog=\"Example: pod5 convert fast5 input.fast5 --output output.pod5\",\n        formatter_class=SubcommandHelpFormatter,\n    )\n    parser.add_argument(\n        \"-v\",\n        \"--version\",\n        action=\"version\",\n        version=\"Pod5 version: {}\".format(__version__),\n        help=\"Show pod5 version and exit.\",\n    )\n    parser.set_defaults(func=lambda **_: parser.print_help())\n\n    root = parser.add_subparsers(title=\"sub-commands\")\n\n    # add sub-parsers to the root argparser\n    prepare_pod5_convert(root)\n    prepare_pod5_inspect_argparser(root)\n    prepare_pod5_merge_argparser(root)\n    prepare_pod5_repack_argparser(root)\n    prepare_pod5_subset_argparser(root)\n    prepare_pod5_filter_argparser(root)\n    prepare_pod5_recover_argparser(root)\n    prepare_pod5_update_argparser(root)\n    prepare_pod5_view_argparser(root)\n\n    # Run the tool\n    return run_tool(parser)\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/parsers.py",
    "content": "\"\"\"\nParsers for pod5 tools.\n\nEach parser should have set_defaults(func=tool) called where tool is the core function\nof a pod5 tool. These are commonly tool_pod5 (e.g. subset_pod5, merge_pod5)\n\"\"\"\n\nimport argparse\nimport sys\nfrom pathlib import Path\nfrom typing import Any, Optional\n\nfrom pod5.signal_tools import DEFAULT_SIGNAL_CHUNK_SIZE\nfrom pod5.tools.utils import DEFAULT_THREADS, is_pod5_debug\n\n\nclass SubcommandHelpFormatter(\n    argparse.RawDescriptionHelpFormatter,\n    argparse.ArgumentDefaultsHelpFormatter,\n):\n    \"\"\"\n    Helper function to prettier print subcommand help. This removes some\n    extra lines of output when a final command parser is not selected.\n    \"\"\"\n\n    def _format_action(self, action):\n        parts = super(SubcommandHelpFormatter, self)._format_action(action)\n        if action.nargs == argparse.PARSER:\n            parts = \"\\n\".join(parts.split(\"\\n\")[1:])\n        return parts\n\n\ndef run_tool(parser: argparse.ArgumentParser) -> Any:\n    \"\"\"Run the tool prepared by an argparser\"\"\"\n    kwargs = vars(parser.parse_args())\n    tool_func = kwargs.pop(\"func\")\n    try:\n        return tool_func(**kwargs)\n    except Exception as exc:\n        if is_pod5_debug():\n            raise exc\n        print(f\"\\nPOD5 has encountered an error: '{exc}'\", file=sys.stderr)\n        print(\"\\nFor detailed information set POD5_DEBUG=1'\", file=sys.stderr)\n        exit(1)\n\n\ndef add_recursive_argument(parser: argparse.ArgumentParser):\n    parser.add_argument(\n        \"-r\",\n        \"--recursive\",\n        default=False,\n        action=\"store_true\",\n        help=\"Search for input files recursively matching `*.pod5`\",\n    )\n\n\ndef add_force_overwrite_argument(parser: argparse._ActionsContainer):\n    parser.add_argument(\n        \"-f\",\n        \"--force-overwrite\",\n        action=\"store_true\",\n        help=\"Overwrite destination files\",\n    )\n\n\n#\n# CONVERT - fast5\n#\ndef pod5_convert_from_fast5_argparser(\n    parent: Optional[argparse._SubParsersAction] = None,\n) -> argparse.ArgumentParser:\n    \"\"\"\n    Create an argument parser for the pod5 convert-from-fast5 tool\n    \"\"\"\n\n    _desc = \"Convert fast5 file(s) into a pod5 file(s)\"\n\n    if parent is None:\n        parser = argparse.ArgumentParser(description=_desc)\n    else:\n        parser = parent.add_parser(\n            name=\"fast5\",\n            aliases=[\"from_fast5\"],\n            description=_desc,\n            formatter_class=SubcommandHelpFormatter,\n        )\n\n    parser.add_argument(\n        \"inputs\", type=Path, nargs=\"+\", help=\"Input path for fast5 file\"\n    )\n    required_group = parser.add_argument_group(\"required arguments\")\n    required_group.add_argument(\n        \"-o\",\n        \"--output\",\n        type=Path,\n        required=True,\n        help=\"Output path for the pod5 file(s). This can be an existing \"\n        \"directory (creating 'output.pod5' within it) or a new named file path. \"\n        \"A directory must be given when using --one-to-one.\",\n    )\n    add_recursive_argument(parser)\n    parser.add_argument(\n        \"-t\",\n        \"--threads\",\n        default=DEFAULT_THREADS,\n        type=int,\n        help=\"Set the number of threads to use\",\n    )\n    parser.add_argument(\n        \"--strict\",\n        action=\"store_true\",\n        help=\"Immediately quit if an exception is encountered during conversion \"\n        \"instead of continuing with remaining inputs after issuing a warning\",\n    )\n    output_group = parser.add_argument_group(\"output control arguments\")\n    output_group.add_argument(\n        \"-O\",\n        \"--one-to-one\",\n        type=Path,\n        default=None,\n        help=\"Output files are written 1:1 to inputs. 1:1 output files are \"\n        \"written to the output directory in a new directory structure relative to the \"\n        \"directory path provided to this argument. This directory path must be a \"\n        \"relative parent of all inputs.\",\n    )\n    add_force_overwrite_argument(output_group)\n    output_group.add_argument(\n        \"--signal-chunk-size\",\n        default=DEFAULT_SIGNAL_CHUNK_SIZE,\n        help=\"Chunk size to use for signal data set\",\n        type=int,\n    )\n\n    def run(**kwargs):\n        from pod5.tools.pod5_convert_from_fast5 import convert_from_fast5\n\n        return convert_from_fast5(**kwargs)\n\n    parser.set_defaults(func=run)\n\n    return parser\n\n\n#\n# CONVERT - to_fast5\n#\ndef pod5_convert_to_fast5_argparser(\n    parent: Optional[argparse._SubParsersAction] = None,\n) -> argparse.ArgumentParser:\n    \"\"\"\n    Create an argument parser for the pod5 convert-to-fast5 tool\n    \"\"\"\n    _desc = \"Convert pod5 file(s) into fast5 file(s)\"\n    if parent is None:\n        parser = argparse.ArgumentParser(description=_desc)\n    else:\n        parser = parent.add_parser(\n            name=\"to_fast5\",\n            description=_desc,\n            formatter_class=SubcommandHelpFormatter,\n        )\n\n    parser.add_argument(\"inputs\", type=Path, nargs=\"+\")\n    required_group = parser.add_argument_group(\"required arguments\")\n    required_group.add_argument(\n        \"-o\",\n        \"--output\",\n        type=Path,\n        required=True,\n        help=\"Output path for the pod5 file(s). This can be an existing \"\n        \"directory (creating 'output.pod5' within it) or a new named file path. \"\n        \"A directory must be given when using --one-to-one.\",\n    )\n    add_recursive_argument(parser)\n    parser.add_argument(\n        \"-t\",\n        \"--threads\",\n        default=DEFAULT_THREADS,\n        type=int,\n        help=\"How many file writers to keep active\",\n    )\n    output_group = parser.add_argument_group(\"output control arguments\")\n    add_force_overwrite_argument(output_group)\n    output_group.add_argument(\n        \"--file-read-count\",\n        default=4000,\n        type=int,\n        help=\"Number of reads to write per file.\",\n    )\n\n    def run(**kwargs):\n        from pod5.tools.pod5_convert_to_fast5 import convert_to_fast5\n\n        return convert_to_fast5(**kwargs)\n\n    parser.set_defaults(func=run)\n\n    return parser\n\n\n#\n# CONVERT - root\n#\ndef prepare_pod5_convert(parent: argparse._SubParsersAction) -> argparse.ArgumentParser:\n    \"\"\"Create an argument paraser for the pod5 convert entry point\"\"\"\n\n    _desc = \"File conversion tools\"\n\n    convert_parser = parent.add_parser(\n        name=\"convert\",\n        description=_desc,\n        epilog=\"Example: pod5 convert fast5 input.fast5 --output output.pod5\",\n        formatter_class=SubcommandHelpFormatter,\n    )\n    convert_parser.set_defaults(func=lambda x: convert_parser.print_help())\n\n    sub_convert_parser = convert_parser.add_subparsers(title=\"conversion type\")\n\n    pod5_convert_from_fast5_argparser(sub_convert_parser)\n    pod5_convert_to_fast5_argparser(sub_convert_parser)\n\n    return convert_parser\n\n\n#\n# Filter\n#\ndef prepare_pod5_filter_argparser(\n    parent: Optional[argparse._SubParsersAction] = None,\n) -> argparse.ArgumentParser:\n    \"\"\"Create an argument parser for the pod5 filter tool\"\"\"\n\n    _desc = \"Take a subset of reads using a list of read_ids from one or more inputs\"\n    if parent is None:\n        parser = argparse.ArgumentParser(description=_desc)\n    else:\n        parser = parent.add_parser(\n            name=\"filter\",\n            description=_desc,\n            epilog=\"Example: pod5 filter inputs*.pod5 --ids read_ids.txt --output filtered.pod5\",\n        )\n\n    # Core arguments\n    parser.add_argument(\n        \"inputs\", type=Path, nargs=\"+\", help=\"Pod5 filepaths to use as inputs\"\n    )\n    add_recursive_argument(parser)\n    add_force_overwrite_argument(parser)\n\n    required_group = parser.add_argument_group(\"required arguments\")\n    required_group.add_argument(\n        \"-i\",\n        \"--ids\",\n        type=Path,\n        required=True,\n        help=\"A file containing a list of only valid read ids to filter from inputs\",\n    )\n    required_group.add_argument(\n        \"-o\",\n        \"--output\",\n        type=Path,\n        required=True,\n        help=\"Destination output filename\",\n    )\n    parser.add_argument(\n        \"-t\",\n        \"--threads\",\n        type=int,\n        default=DEFAULT_THREADS,\n        help=\"Number of workers\",\n    )\n\n    content_group = parser.add_argument_group(\"content settings\")\n    content_group.add_argument(\n        \"-M\",\n        \"--missing-ok\",\n        action=\"store_true\",\n        help=\"Allow missing read_ids\",\n    )\n\n    def run(**kwargs):\n        from pod5.tools.pod5_filter import filter_pod5\n\n        return filter_pod5(**kwargs)\n\n    parser.set_defaults(func=run)\n\n    return parser\n\n\n#\n# Inspect\n#\ndef prepare_pod5_inspect_argparser(\n    parent: Optional[argparse._SubParsersAction] = None,\n) -> argparse.ArgumentParser:\n    \"\"\"Create an argument parser for the pod5 inspect tool\"\"\"\n\n    _desc = \"Inspect the contents of a pod5 file\"\n    if parent is None:\n        parser = argparse.ArgumentParser(description=_desc)\n    else:\n        parser = parent.add_parser(\n            name=\"inspect\",\n            description=_desc,\n            epilog=\"Example: pod5 inspect reads input.pod5\",\n        )\n\n    def run(**kwargs):\n        from pod5.tools.pod5_inspect import inspect_pod5\n\n        return inspect_pod5(**kwargs)\n\n    subparser = parser.add_subparsers(title=\"command\", dest=\"command\")\n    summary_parser = subparser.add_parser(\n        \"summary\",\n        description=\"Print a summary of the contents of pod5 files\",\n        epilog=\"Example: pod5 inspect summary input.pod5\",\n    )\n    summary_parser.add_argument(\"input_files\", type=Path, nargs=\"+\")\n    summary_parser.set_defaults(func=run)\n\n    reads_parser = subparser.add_parser(\n        \"reads\",\n        description=\"Print read information on all reads as a csv table\",\n        epilog=\"Example: pod5 inspect reads input.pod5\",\n    )\n    reads_parser.add_argument(\"input_files\", type=Path, nargs=\"+\")\n    add_recursive_argument(reads_parser)\n    reads_parser.set_defaults(func=run)\n\n    read_parser = subparser.add_parser(\n        \"read\",\n        description=\"Print detailed read information for a named read id\",\n        epilog=\"Example: pod5 inspect read input.pod5 0000173c-bf67-44e7-9a9c-1ad0bc728e74\",\n    )\n    read_parser.add_argument(\"input_files\", type=Path, nargs=1)\n    read_parser.add_argument(\"read_id\", type=str)\n    read_parser.set_defaults(func=run, recursive=False)\n\n    debug_parser = subparser.add_parser(\n        \"debug\",\n        description=\"Print debugging information\",\n        epilog=\"Example: pod5 inspect debug input.pod5\",\n    )\n    debug_parser.add_argument(\"input_files\", type=Path, nargs=1)\n    debug_parser.set_defaults(func=run)\n\n    return parser\n\n\n#\n# MERGE\n#\ndef prepare_pod5_merge_argparser(\n    parent: Optional[argparse._SubParsersAction] = None,\n) -> argparse.ArgumentParser:\n    \"\"\"Create an argument parser for the pod5 merge tool\"\"\"\n\n    _desc = \"Merge multiple pod5 files\"\n\n    if parent is None:\n        parser = argparse.ArgumentParser(description=_desc)\n    else:\n        parser = parent.add_parser(\n            name=\"merge\",\n            description=_desc,\n            formatter_class=SubcommandHelpFormatter,\n            epilog=\"Example: pod5 merge inputs/*.pod5 merged.pod5\",\n        )\n\n    # Core arguments\n    parser.add_argument(\n        \"inputs\",\n        type=Path,\n        nargs=\"+\",\n        help=\"Pod5 filepaths to use as inputs\",\n    )\n    parser.add_argument(\n        \"-o\",\n        \"--output\",\n        required=True,\n        type=Path,\n        help=\"Output filepath\",\n    )\n    add_recursive_argument(parser)\n    add_force_overwrite_argument(parser)\n    parser.add_argument(\n        \"-t\",\n        \"--threads\",\n        type=int,\n        default=DEFAULT_THREADS,\n        help=\"Number of workers\",\n    )\n    parser.add_argument(\n        \"-R\",\n        \"--readers\",\n        type=int,\n        default=20,\n        help=\"number of merge readers TESTING ONLY\",\n    )\n\n    def run(**kwargs):\n        from pod5.tools.pod5_merge import merge_pod5\n\n        return merge_pod5(**kwargs)\n\n    parser.set_defaults(func=run)\n    return parser\n\n\n#\n# Repack\n#\ndef prepare_pod5_repack_argparser(\n    parent: Optional[argparse._SubParsersAction] = None,\n) -> argparse.ArgumentParser:\n    \"\"\"Create an argument parser for the pod5 repack tool\"\"\"\n\n    _desc = \"Repack a pod5 files into a single output\"\n    if parent is None:\n        parser = argparse.ArgumentParser(description=_desc)\n    else:\n        parser = parent.add_parser(\n            name=\"repack\",\n            description=_desc,\n            epilog=\"Example: pod5 repack inputs/*.pod5 repacked/\",\n        )\n\n    parser.add_argument(\n        \"inputs\", type=Path, nargs=\"+\", help=\"Input pod5 file(s) to repack\"\n    )\n    parser.add_argument(\n        \"-o\", \"--output\", type=Path, help=\"Output directory for pod5 files\"\n    )\n    add_recursive_argument(parser)\n    add_force_overwrite_argument(parser)\n    parser.add_argument(\n        \"-t\",\n        \"--threads\",\n        type=int,\n        default=DEFAULT_THREADS,\n        help=\"Number of repacking workers\",\n    )\n\n    def run(**kwargs):\n        from pod5.tools.pod5_repack import repack_pod5\n\n        return repack_pod5(**kwargs)\n\n    parser.set_defaults(func=run)\n    return parser\n\n\n#\n# Subset\n#\ndef prepare_pod5_subset_argparser(\n    parent: Optional[argparse._SubParsersAction] = None,\n) -> argparse.ArgumentParser:\n    \"\"\"Create an argument parser for the pod5 subset tool\"\"\"\n\n    _desc = (\n        \"Given one or more pod5 input files, take subsets of reads \"\n        \"into one or more pod5 output files by a user-supplied mapping.\"\n    )\n    if parent is None:\n        parser = argparse.ArgumentParser(description=_desc)\n    else:\n        parser = parent.add_parser(\n            name=\"subset\",\n            description=_desc,\n            formatter_class=SubcommandHelpFormatter,\n            epilog=\"Example: pod5 subset inputs.pod5 --output subset_mux/ \"\n            \"--summary summary.tsv --columns mux\",\n        )\n\n    # Core arguments\n    parser.add_argument(\n        \"inputs\", type=Path, nargs=\"+\", help=\"Pod5 filepaths to use as inputs\"\n    )\n    parser.add_argument(\n        \"-o\",\n        \"--output\",\n        type=Path,\n        default=Path.cwd(),\n        help=\"Destination directory to write outputs\",\n    )\n    add_recursive_argument(parser)\n    add_force_overwrite_argument(parser)\n    parser.add_argument(\n        \"-t\",\n        \"--threads\",\n        type=int,\n        default=DEFAULT_THREADS,\n        help=\"Number of subsetting workers\",\n    )\n\n    mapping_group = parser.add_argument_group(\"direct mapping\")\n    mapping_exclusive = mapping_group.add_mutually_exclusive_group(required=False)\n    mapping_exclusive.add_argument(\n        \"--csv\",\n        type=Path,\n        help=\"CSV file mapping output filename to read ids\",\n    )\n\n    table_group = parser.add_argument_group(\"table mapping\")\n\n    # Allow --summary or --table\n    table_group.add_argument(\n        \"-s\",\n        \"--summary\",\n        \"--table\",\n        type=Path,\n        help=\"Table filepath (csv or tsv)\",\n        dest=\"table\",\n    )\n    table_group.add_argument(\n        \"-R\",\n        \"--read-id-column\",\n        type=str,\n        default=\"read_id\",\n        help=\"Name of the read_id column in the summary\",\n    )\n    table_group.add_argument(\n        \"-c\",\n        \"--columns\",\n        type=str,\n        nargs=\"+\",\n        help=\"Names of --summary / --table columns to subset on\",\n    )\n    table_group.add_argument(\n        \"--template\",\n        type=str,\n        default=None,\n        help=\"template string to generate output filenames \"\n        '(e.g. \"mux-{mux}_barcode-{barcode}.pod5\"). '\n        \"default is to concatenate all columns to values as shown in the example.\",\n    )\n    table_group.add_argument(\n        \"-T\",\n        \"--ignore-incomplete-template\",\n        action=\"store_true\",\n        default=None,\n        help=\"Suppress the exception raised if the --template string does not contain \"\n        \"every --columns key\",\n    )\n\n    content_group = parser.add_argument_group(\"content settings\")\n    content_group.add_argument(\n        \"-M\",\n        \"--missing-ok\",\n        action=\"store_true\",\n        help=\"Allow missing read_ids\",\n    )\n\n    def run(**kwargs):\n        from pod5.tools.pod5_subset import subset_pod5\n\n        return subset_pod5(**kwargs)\n\n    parser.set_defaults(func=run)\n\n    return parser\n\n\n#\n# Recover\n#\ndef prepare_pod5_recover_argparser(\n    parent: Optional[argparse._SubParsersAction] = None,\n) -> argparse.ArgumentParser:\n    \"\"\"Create an argument parser for the pod5 recover tool\"\"\"\n\n    _desc = (\n        \"Attempt to recover pod5 files. Recovered files are written \"\n        \"to sibling files with the '_recovered.pod5` suffix\"\n    )\n    if parent is None:\n        parser = argparse.ArgumentParser(description=_desc)\n    else:\n        parser = parent.add_parser(name=\"recover\", description=_desc)\n\n    parser.add_argument(\n        \"--cleanup\",\n        action=\"store_true\",\n        help=\"Delete successfully recovered input files and files with no data to recover.\",\n    )\n\n    parser.add_argument(\n        \"inputs\", type=Path, nargs=\"+\", help=\"Input pod5 file(s) to update\"\n    )\n\n    add_recursive_argument(parser)\n    add_force_overwrite_argument(parser)\n\n    def run(**kwargs) -> Any:\n        from pod5.tools.pod5_recover import recover_pod5\n\n        return recover_pod5(**kwargs)\n\n    parser.set_defaults(func=run)\n\n    return parser\n\n\n#\n# Update\n#\ndef prepare_pod5_update_argparser(\n    parent: Optional[argparse._SubParsersAction] = None,\n) -> argparse.ArgumentParser:\n    \"\"\"Create an argument parser for the pod5 update tool\"\"\"\n\n    _desc = \"Update a pod5 files to the latest available version\"\n    if parent is None:\n        parser = argparse.ArgumentParser(description=_desc)\n    else:\n        parser = parent.add_parser(\n            name=\"update\",\n            description=_desc,\n            formatter_class=SubcommandHelpFormatter,\n        )\n\n    parser.add_argument(\n        \"inputs\", type=Path, nargs=\"+\", help=\"Input pod5 file(s) to update\"\n    )\n    parser.add_argument(\n        \"-o\",\n        \"--output\",\n        type=Path,\n        help=\"Output directory for updated pod5 files\",\n        required=True,\n    )\n\n    add_recursive_argument(parser)\n    add_force_overwrite_argument(parser)\n\n    def run(**kwargs) -> Any:\n        from pod5.tools.pod5_update import update_pod5\n\n        return update_pod5(**kwargs)\n\n    parser.set_defaults(func=run)\n\n    return parser\n\n\n#\n# View\n#\ndef prepare_pod5_view_argparser(\n    parent: Optional[argparse._SubParsersAction] = None,\n) -> argparse.ArgumentParser:\n    \"\"\"Create an argument parser for the pod5 view tool\"\"\"\n\n    _desc = \"\"\"\n    Write contents of some pod5 file(s) as a table to stdout or --output if given.\n    The default separator is <tab>.\n    The column order is always as shown in -L/--list-fields\"\n    \"\"\"\n\n    if parent is None:\n        parser = argparse.ArgumentParser(description=_desc)\n    else:\n        parser = parent.add_parser(\n            name=\"view\",\n            description=_desc,\n            epilog=\"Example: pod5 view input.pod5\",\n            formatter_class=SubcommandHelpFormatter,\n        )\n\n    parser.add_argument(\n        \"inputs\", type=Path, nargs=\"*\", help=\"Input pod5 file(s) to view\"\n    )\n\n    parser.add_argument(\n        \"-o\", \"--output\", type=Path, default=None, help=\"Output filename\"\n    )\n    add_recursive_argument(parser)\n    add_force_overwrite_argument(parser)\n    parser.add_argument(\n        \"-t\",\n        \"--threads\",\n        default=DEFAULT_THREADS,\n        type=int,\n        help=\"Set the number of reader workers\",\n    )\n    format_group = parser.add_argument_group(\"Formatting\")\n    format_group.add_argument(\n        \"-H\", \"--no-header\", action=\"store_true\", help=\"Omit the header line\"\n    )\n    format_group.add_argument(\n        \"--separator\",\n        default=\"\\t\",\n        help=\"Table separator character (e.g. ',')\",\n        type=str,\n    )\n\n    selection = parser.add_argument_group(\"Selection\")\n    selection.add_argument(\n        \"-I\",\n        \"--ids\",\n        action=\"store_true\",\n        help=\"Only write 'read_id' field\",\n        dest=\"group_read_id\",\n    )\n    selection.add_argument(\n        \"-i\",\n        \"--include\",\n        type=str,\n        help=\"Include a double-quoted comma-separated list of fields\",\n    )\n    selection.add_argument(\n        \"-x\",\n        \"--exclude\",\n        type=str,\n        help=\"Exclude a double-quoted comma-separated list of fields.\",\n    )\n\n    help_group = parser.add_argument_group(\"List Fields\")\n    help_group.add_argument(\n        \"-L\",\n        \"--list-fields\",\n        action=\"store_true\",\n        help=\"List all groups and fields available for selection and exit\",\n    )\n\n    def run(**kwargs):\n        from pod5.tools.pod5_view import view_pod5\n\n        return view_pod5(**kwargs)\n\n    parser.set_defaults(func=run)\n\n    return parser\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/pod5_convert_from_fast5.py",
    "content": "\"\"\"\nTool for converting fast5 files to the pod5 format\n\"\"\"\n\nimport datetime\nimport multiprocessing as mp\nfrom multiprocessing.context import SpawnContext\nimport sys\nimport warnings\nfrom pod5.pod5_types import CompressedRead\nfrom tqdm.auto import tqdm\nimport uuid\nfrom pathlib import Path\nfrom queue import Empty\nfrom typing import (\n    Any,\n    Collection,\n    Dict,\n    Iterable,\n    List,\n    Optional,\n    Sequence,\n    Tuple,\n    Union,\n)\n\nimport h5py\nimport iso8601\nimport more_itertools\nimport vbz_h5py_plugin  # noqa: F401\n\nimport pod5 as p5\nfrom pod5.signal_tools import DEFAULT_SIGNAL_CHUNK_SIZE, vbz_compress_signal_chunked\nfrom pod5.tools.parsers import pod5_convert_from_fast5_argparser, run_tool\nfrom pod5.tools.utils import (\n    DEFAULT_THREADS,\n    PBAR_DEFAULTS,\n    collect_inputs,\n    init_logging,\n    limit_threads,\n    logged,\n    logged_all,\n    terminate_processes,\n)\n\nREAD_CHUNK_SIZE = 400\nTIMEOUT_SECONDS = 600\n\n\nlogger = init_logging()\n\n\nclass QueueManager:\n    def __init__(\n        self,\n        context: SpawnContext,\n        inputs: Collection[Path],\n        threads: int,\n        timeout: float,\n    ) -> None:\n        \"\"\"Manager for balancing work queues\"\"\"\n        self._requests_size = threads * 2\n        self._inputs: mp.Queue = context.Queue(maxsize=len(inputs))\n        self._requests: mp.Queue = context.Queue(maxsize=self._requests_size)\n        self._data: mp.Queue = context.Queue()\n        self._exceptions: mp.Queue = context.Queue()\n        self._timeout = timeout\n\n        self._start(inputs=inputs)\n\n    def _await(self, queue: mp.Queue) -> Any:\n        \"\"\"Await the next item on a queue raising TimeoutError if failing\"\"\"\n        try:\n            item = queue.get(timeout=self._timeout)\n            return item\n        except Empty:\n            logger.fatal(\"Empty queue or timeout \")\n            raise TimeoutError(f\"No progress in {self._timeout} seconds - quitting\")\n\n    def enqueue_request(self) -> None:\n        self._requests.put(None, timeout=self._timeout)\n\n    def await_request(self) -> None:\n        \"\"\"Await a request for data\"\"\"\n        self._await(self._requests)\n\n    @logged()\n    def enqueue_data(\n        self, path: Optional[Path], reads: Union[List[CompressedRead], int, None]\n    ) -> None:\n        \"\"\"\n        Enqueues an input path and either a list of compressed reads to be written, or\n        the total count of reads converted for that path.\n        Otherwise, if path is None, mark the child process as being empty.\n        \"\"\"\n        self._data.put((path, reads), timeout=self._timeout)\n\n    @logged(log_time=True)\n    def await_data(\n        self,\n    ) -> Tuple[Optional[Path], Union[List[CompressedRead], int, None]]:\n        \"\"\"\n        Await compressed reads or the total count of reads compressed (file end) for\n        a input filepath. Enqueues the next request if necessary\n        \"\"\"\n        path, item = self._await(self._data)\n\n        # Check for the exhausted process sentinel value\n        if path is None:\n            return None, None\n\n        # Add another request if we received compressed reads\n        if isinstance(item, List):\n            self.enqueue_request()\n\n        return path, item\n\n    @logged(log_args=True)\n    def enqueue_exception(self, path: Path, exception: Exception, trace: str) -> None:\n        self._exceptions.put((path, exception, trace), timeout=self._timeout)\n\n    def get_exception(self) -> Optional[Tuple[Path, Exception, str]]:\n        \"\"\"Promptly get an exception if any\"\"\"\n        try:\n            # Use short timeout instead of get_nowait as we might call this method\n            # very shortly after enqueueing an exception\n            path, exc, trace = self._exceptions.get(timeout=0.01)\n            logger.exception(f\"Encountered an exception in {path} - {exc}\")\n            if trace:\n                logger.exception(f\"Trace Exception {path}\\n{trace}\")\n            return path, exc, trace\n        except Empty:\n            pass\n        return None\n\n    @logged(log_args=True)\n    def enqueue_input(self, path: Path) -> None:\n        \"\"\"Enqueue a request\"\"\"\n        self._inputs.put(path)\n\n    @logged_all\n    def get_input(self) -> Optional[Path]:\n        \"\"\"Promptly get an input if any returning None if queue is empty\"\"\"\n        try:\n            return self._inputs.get(timeout=0.1)\n        except Empty:\n            pass\n        return None\n\n    @logged(log_return=True)\n    def _discard_and_close(self, queue: mp.Queue) -> int:\n        \"\"\"\n        Discard all remaining enqueued items and close a queue to nicely shutdown the\n        queue. Returns the number of discarded items\n        \"\"\"\n        count = 0\n        while True:\n            try:\n                queue.get(timeout=0.1)\n                count += 1\n            except Exception:\n                break\n        queue.close()\n        queue.join_thread()\n        return count\n\n    @logged(log_return=True)\n    def shutdown(self) -> Tuple[int, int, int, int]:\n        \"\"\"Shutdown all queues returning the counts of all remaining items\"\"\"\n        n_inputs = self._discard_and_close(self._inputs)\n        n_req = self._discard_and_close(self._requests)\n        n_data = self._discard_and_close(self._data)\n        n_exc = self._discard_and_close(self._exceptions)\n\n        if n_inputs > 0:\n            logger.warn(\"Unfinished inputs found during shutdown!\")\n        if n_data > 0:\n            logger.warn(\"Unfinished data found during shutdown!\")\n        if n_exc > 0:\n            logger.warn(\"Unfinished exceptions found during shutdown!\")\n\n        return n_inputs, n_req, n_data, n_exc\n\n    @logged(log_args=True)\n    def _start(self, inputs: Iterable[Path]) -> None:\n        \"\"\"Enqueue all inputs for child processes to poll and set the requests size\"\"\"\n        for path in inputs:\n            if path.is_file():\n                self.enqueue_input(path)\n\n        for _ in range(self._requests_size):\n            self.enqueue_request()\n\n\nclass OutputHandler:\n    \"\"\"Class for managing p5.Writer handles\"\"\"\n\n    @logged(log_args=True)\n    def __init__(\n        self,\n        output_root: Path,\n        one_to_one: Optional[Path],\n        force_overwrite: bool,\n    ):\n        self.output_root = output_root\n        self._one_to_one = one_to_one\n        self._force_overwrite = force_overwrite\n        self._input_to_output: Dict[Path, Path] = {}\n        self._open_writers: Dict[Path, p5.Writer] = {}\n        self._closed_writers: Dict[Path, bool] = {}\n\n    @logged_all\n    def _open_writer(self, output_path: Path) -> Optional[p5.Writer]:\n        \"\"\"Get the writer from existing handles or create a new one if unseen\"\"\"\n        if output_path in self._open_writers:\n            return self._open_writers[output_path]\n\n        if output_path in self._closed_writers:\n            had_exception = self._closed_writers[output_path]\n            if had_exception:\n                return None\n            raise FileExistsError(f\"Trying to re-open a closed Writer to {output_path}\")\n\n        if output_path.exists() and self._force_overwrite:\n            output_path.unlink()\n\n        writer = p5.Writer(output_path)\n        self._open_writers[output_path] = writer\n        return writer\n\n    @logged_all\n    def get_writer(self, input_path: Path) -> Optional[p5.Writer]:\n        \"\"\"Get a Pod5Writer to write data from the input_path\"\"\"\n        if input_path not in self._input_to_output:\n            out_path = self.resolve_output_path(\n                path=input_path, root=self.output_root, relative_root=self._one_to_one\n            )\n            self._input_to_output[input_path] = out_path\n\n        output_path = self._input_to_output[input_path]\n        return self._open_writer(output_path=output_path)\n\n    @staticmethod\n    @logged_all\n    def resolve_one_to_one_path(path: Path, root: Path, relative_root: Path):\n        \"\"\"\n        Find the relative path between the input path and the relative root\n        \"\"\"\n        try:\n            relative = path.with_suffix(\".pod5\").relative_to(relative_root)\n        except ValueError as exc:\n            raise RuntimeError(\n                f\"--one-to-one directory must be a relative parent of \"\n                f\"all input fast5 files. For {path} relative to {relative_root}\"\n            ) from exc\n\n        # Resolve the new final output path relative to the output directory\n        # This path is to a file with the equivalent filename(.pod5)\n        return root / relative\n\n    @staticmethod\n    @logged_all\n    def resolve_output_path(\n        path: Path, root: Path, relative_root: Optional[Path]\n    ) -> Path:\n        \"\"\"\n        Resolve the output path. If relative_root is a path, resolve the relative output\n        path under root, otherwise, the output is either root or a new file within root\n        if root is a directory\n        \"\"\"\n        if relative_root is not None:\n            # Resolve the relative path to the one_to_one root path\n            out_path = OutputHandler.resolve_one_to_one_path(\n                path=path,\n                root=root,\n                relative_root=relative_root,\n            )\n\n            # Create directory structure if needed\n            out_path.parent.mkdir(parents=True, exist_ok=True)\n            return out_path\n\n        if root.is_dir():\n            # If the output path is a directory, the write the default filename\n            return root / \"output.pod5\"\n\n        # The provided output path is assumed to be a named file\n        return root\n\n    @logged(log_args=True)\n    def set_input_complete(self, input_path: Path, is_exception: bool) -> None:\n        \"\"\"Close the Pod5Writer for associated input_path\"\"\"\n        if not self._one_to_one:\n            # Do not close common output file when not in 1-2-1 mode\n            return\n\n        if input_path not in self._input_to_output:\n            return\n\n        output_path = self._input_to_output[input_path]\n        self._open_writers[output_path].close()\n        self._closed_writers[output_path] = is_exception\n        del self._open_writers[output_path]\n\n    @logged()\n    def close_all(self):\n        \"\"\"Close all open writers\"\"\"\n        for path, writer in self._open_writers.items():\n            try:\n                writer.close()\n                del writer\n                # Keep track of closed writers to ensure we don't overwrite our own work\n                self._closed_writers[path] = False\n            except Exception as exc:\n                logger.debug(f\"Failed to cleanly close writer to {path} - {exc}\")\n        self._open_writers = {}\n\n\nclass StatusMonitor:\n    \"\"\"Class for monitoring the status of the conversion\"\"\"\n\n    @logged_all\n    def __init__(self, paths: Sequence[Path]):\n        # Estimate that a fast5 file will have 4k reads\n        self.path_reads = {path: 4000 for path in paths}\n        self.count_finished = 0\n\n        self.pbar = tqdm(\n            total=self.total_reads,\n            desc=f\"Converting {len(self.path_reads)} Fast5s\",\n            unit=\"Reads\",\n            leave=True,\n            **PBAR_DEFAULTS,\n        )\n\n    @property\n    def total_files(self) -> int:\n        return len(self.path_reads)\n\n    @property\n    def total_reads(self) -> int:\n        return sum(self.path_reads.values())\n\n    @logged(log_args=True)\n    def increment_reads(self, n: int) -> None:\n        \"\"\"Increment the reads status by n\"\"\"\n        self.pbar.update(n)\n\n    @logged(log_args=True)\n    def update_reads_total(self, path: Path, total: int) -> None:\n        \"\"\"Increment the reads status by n and update the total reads\"\"\"\n        self.path_reads[path] = total\n        self.pbar.total = self.total_reads\n        self.pbar.refresh()\n\n    @logged(log_args=True)\n    def write(self, msg: str, file: Any) -> None:\n        \"\"\"Write runtime message to avoid clobbering tqdm pbar\"\"\"\n        self.pbar.write(msg, file=file)\n\n    @logged()\n    def close(self) -> None:\n        \"\"\"Close the progress bar\"\"\"\n        self.pbar.close()\n\n\n@logged_all\ndef is_multi_read_fast5(path: Path) -> bool:\n    \"\"\"\n    Assert that the given path points to a a multi-read fast5 file for which\n    direct-to-pod5 conversion is supported.\n    \"\"\"\n    try:\n        with h5py.File(path) as _h5:\n            # The \"file_type\" attribute might be present on supported multi-read fast5 files.\n            if _h5.attrs.get(\"file_type\") == \"multi-read\":\n                return True\n\n            # No keys, assume multi-read but there shouldn't be anything to do which would\n            # cause an issue so pass silently\n            if len(_h5) == 0:\n                return True\n\n            # if there are \"read_x\" keys, this is a multi-read file\n            if any(key for key in _h5 if key.startswith(\"read_\")):\n                return True\n\n    except Exception:\n        pass\n\n    return False\n\n\ndef decode_str(value: Union[str, bytes]) -> str:\n    \"\"\"Decode a h5py utf-8 byte string to python string\"\"\"\n    if isinstance(value, str):\n        return value\n    return value.decode(\"utf-8\")\n\n\ndef convert_fast5_end_reason(fast5_end_reason: int) -> p5.EndReason:\n    \"\"\"\n    Return an EndReason instance from the given end_reason integer from a fast5 file.\n    This will handle the difference between fast5 and pod5 values for this enumeration\n    and set the default \"forced\" value for each fast5 enumeration value.\n    \"\"\"\n    # Expected fast5 enumeration:\n    # end_reason_dict = {\n    #     \"unknown\": 0,\n    #     \"partial\": 1, <-- Not used in pod5\n    #     \"mux_change\": 2,  <-- Remaining values are offset by +1\n    #     \"unblock_mux_change\": 3,\n    #     \"data_service_unblock_mux_change\": 4,\n    #     \"signal_positive\": 5,\n    #     \"signal_negative\": 6,\n    # }\n\n    # (0:unknown | 1:partial) => pod5 (0:unknown)\n    if fast5_end_reason < 2:\n        return p5.EndReason.from_reason_with_default_forced(p5.EndReasonEnum.UNKNOWN)\n\n    # Resolve the offset in enumeration values between both files\n    p5_scaled_end_reason = fast5_end_reason - 1\n    return p5.EndReason.from_reason_with_default_forced(\n        p5.EndReasonEnum(p5_scaled_end_reason)\n    )\n\n\ndef convert_datetime_as_epoch_ms(\n    time_str: Union[str, bytes, None],\n) -> datetime.datetime:\n    \"\"\"Convert the fast5 time string to timestamp\"\"\"\n    epoch = datetime.datetime.utcfromtimestamp(0).replace(tzinfo=datetime.timezone.utc)\n    if time_str is None:\n        return epoch\n    try:\n        return iso8601.parse_date(decode_str(time_str))\n    except iso8601.iso8601.ParseError:\n        return epoch\n\n\ndef convert_run_info(\n    acq_id: str,\n    adc_max: int,\n    adc_min: int,\n    sample_rate: int,\n    context_tags: Dict[str, Union[str, bytes]],\n    device_type: str,\n    tracking_id: Dict[str, Union[str, bytes]],\n) -> p5.RunInfo:\n    \"\"\"Create a Pod5RunInfo instance from parsed fast5 data\"\"\"\n    return p5.RunInfo(\n        acquisition_id=acq_id,\n        acquisition_start_time=convert_datetime_as_epoch_ms(\n            tracking_id.get(\"exp_start_time\")\n        ),\n        adc_max=adc_max,\n        adc_min=adc_min,\n        context_tags={\n            str(key): decode_str(value) for key, value in context_tags.items()\n        },\n        experiment_name=\"\",\n        flow_cell_id=decode_str(tracking_id.get(\"flow_cell_id\", b\"\")),\n        flow_cell_product_code=decode_str(\n            tracking_id.get(\"flow_cell_product_code\", b\"\")\n        ),\n        protocol_name=decode_str(tracking_id.get(\"exp_script_name\", b\"\")),\n        protocol_run_id=decode_str(tracking_id.get(\"protocol_run_id\", b\"\")),\n        protocol_start_time=convert_datetime_as_epoch_ms(\n            tracking_id.get(\"protocol_start_time\", None)\n        ),\n        sample_id=decode_str(tracking_id.get(\"sample_id\", b\"\")),\n        sample_rate=sample_rate,\n        sequencing_kit=decode_str(context_tags.get(\"sequencing_kit\", b\"\")),\n        sequencer_position=decode_str(tracking_id.get(\"device_id\", b\"\")),\n        sequencer_position_type=decode_str(tracking_id.get(\"device_type\", device_type)),\n        software=\"python-pod5-converter\",\n        system_name=decode_str(tracking_id.get(\"host_product_serial_number\", b\"\")),\n        system_type=decode_str(tracking_id.get(\"host_product_code\", b\"\")),\n        tracking_id={str(key): decode_str(value) for key, value in tracking_id.items()},\n    )\n\n\ndef convert_fast5_read(\n    fast5_read: h5py.Group,\n    run_info_cache: Dict[str, p5.RunInfo],\n    signal_chunk_size: int = DEFAULT_SIGNAL_CHUNK_SIZE,\n) -> p5.CompressedRead:\n    \"\"\"\n    Given a fast5 read parsed from a fast5 file, return a pod5.Read object.\n    \"\"\"\n    channel_id = fast5_read[\"channel_id\"]\n    raw = fast5_read[\"Raw\"]\n\n    attrs = fast5_read.attrs\n\n    # Get the acquisition id\n    if \"run_id\" in attrs:\n        acq_id = decode_str(attrs[\"run_id\"])\n    else:\n        acq_id = decode_str(fast5_read[\"tracking_id\"].attrs[\"run_id\"])\n\n    # Create new run_info if we've not seen this acquisition id before\n    if acq_id not in run_info_cache:\n        adc_min = 0\n        adc_max = 2047\n        device_type_guess = \"promethion\"\n        if channel_id.attrs[\"digitisation\"] == 8192:\n            adc_min = -4096\n            adc_max = 4095\n            device_type_guess = \"minion\"\n\n        # Add new run_info to cache\n        run_info_cache[acq_id] = convert_run_info(\n            acq_id=acq_id,\n            adc_max=adc_max,\n            adc_min=adc_min,\n            sample_rate=int(channel_id.attrs[\"sampling_rate\"]),\n            context_tags=dict(fast5_read[\"context_tags\"].attrs),\n            device_type=device_type_guess,\n            tracking_id=dict(fast5_read[\"tracking_id\"].attrs),\n        )\n\n    # Process attributes unique to this read\n    read_id = uuid.UUID(decode_str(raw.attrs[\"read_id\"]))\n    pore = p5.Pore(\n        channel=int(channel_id.attrs[\"channel_number\"]),\n        well=raw.attrs[\"start_mux\"],\n        pore_type=decode_str(attrs.get(\"pore_type\", b\"not_set\")),\n    )\n    calibration = p5.Calibration.from_range(\n        offset=channel_id.attrs[\"offset\"],\n        adc_range=channel_id.attrs[\"range\"],\n        digitisation=channel_id.attrs[\"digitisation\"],\n    )\n\n    end_reason = convert_fast5_end_reason(raw.attrs.get(\"end_reason\", 0))\n\n    # Signal conversion process\n    signal = raw[\"Signal\"][()]\n    signal_chunks, signal_chunk_lengths = vbz_compress_signal_chunked(\n        signal, signal_chunk_size\n    )\n\n    return p5.CompressedRead(\n        read_id=read_id,\n        pore=pore,\n        calibration=calibration,\n        read_number=raw.attrs[\"read_number\"],\n        start_sample=raw.attrs[\"start_time\"],\n        median_before=raw.attrs[\"median_before\"],\n        num_minknow_events=raw.attrs.get(\"num_minknow_events\", 0),\n        tracked_scaling=p5.pod5_types.ShiftScalePair(\n            raw.attrs.get(\"tracked_scaling_shift\", float(\"nan\")),\n            raw.attrs.get(\"tracked_scaling_scale\", float(\"nan\")),\n        ),\n        predicted_scaling=p5.pod5_types.ShiftScalePair(\n            raw.attrs.get(\"predicted_scaling_shift\", float(\"nan\")),\n            raw.attrs.get(\"predicted_scaling_scale\", float(\"nan\")),\n        ),\n        num_reads_since_mux_change=raw.attrs.get(\"num_reads_since_mux_change\", 0),\n        time_since_mux_change=raw.attrs.get(\"time_since_mux_change\", 0.0),\n        open_pore_level=float(\"nan\"),  # Not supported in fast5\n        end_reason=end_reason,\n        run_info=run_info_cache[acq_id],\n        signal_chunks=signal_chunks,\n        signal_chunk_lengths=signal_chunk_lengths,\n    )\n\n\ndef get_read_from_fast5(group_name: str, h5_file: h5py.File) -> Optional[h5py.Group]:\n    \"\"\"Read a group from a h5 file ensuring that it's a read\"\"\"\n    if not group_name.startswith(\"read_\"):\n        return None\n\n    try:\n        return h5_file[group_name]\n    except KeyError as exc:\n        # Observed strange behaviour where h5py reports a KeyError with\n        # the message \"Unable to open object\". Report a failed read as warning\n        warnings.warn(\n            f\"Failed to read key {group_name} from {h5_file.filename} : {exc}\",\n        )\n    return None\n\n\ndef convert_fast5_file_chunk(\n    queues: QueueManager,\n    handle: h5py.File,\n    chunk: Iterable[str],\n    cache: Dict[str, p5.RunInfo],\n    signal_chunk_size: int,\n) -> List[CompressedRead]:\n    reads: List[p5.CompressedRead] = []\n\n    # Allow request queue to throttle work\n    queues.await_request()\n    try:\n        for group_name in chunk:\n            f5_read = get_read_from_fast5(group_name, handle)\n            if f5_read is None:\n                continue\n            read = convert_fast5_read(f5_read, cache, signal_chunk_size)\n            reads.append(read)\n\n    except Exception as exc:\n        # Ensures that requests aren't exhausted\n        queues.enqueue_request()\n        raise exc\n    return reads\n\n\n@logged_all\ndef convert_fast5_file(\n    path: Path,\n    queues: QueueManager,\n    signal_chunk_size: int = DEFAULT_SIGNAL_CHUNK_SIZE,\n) -> int:\n    \"\"\"Convert the reads in a fast5 file\"\"\"\n\n    run_info_cache: Dict[str, p5.RunInfo] = {}\n    total_reads: int = 0\n\n    with h5py.File(str(path), \"r\") as _f5:\n        for chunk in more_itertools.chunked(_f5.keys(), READ_CHUNK_SIZE):\n            reads = convert_fast5_file_chunk(\n                queues, _f5, chunk, run_info_cache, signal_chunk_size\n            )\n            queues.enqueue_data(path, reads)\n            total_reads += len(reads)\n\n    return total_reads\n\n\n@logged()\ndef issue_not_multi_read_exception(path: Path, queues: QueueManager):\n    logger.error(f\"Input {path.name} is not a multi-read fast5\")\n    queues.enqueue_exception(\n        path=path,\n        exception=TypeError(f\"{path} is not a multi-read fast5 file.\"),\n        trace=\"\",\n    )\n    logger.info(f\"Enqueueing file end: {path.name} reads: 0\")\n    queues.enqueue_data(path, 0)\n\n\n@logged(log_time=True)\ndef convert_fast5_files(\n    queues: QueueManager,\n    signal_chunk_size: int = DEFAULT_SIGNAL_CHUNK_SIZE,\n) -> None:\n    \"\"\"\n    Main function for converting fast5s available in queues.\n    Collections of converted reads are emplaced on the data_queue for writing in\n    the main process.\n    \"\"\"\n    while True:\n        path = queues.get_input()\n\n        if path is None:\n            logger.info(\"Inputs exhausted. Closing Process\")\n            break\n\n        if not is_multi_read_fast5(path):\n            issue_not_multi_read_exception(path, queues)\n            continue\n\n        try:\n            total_reads = convert_fast5_file(path, queues, signal_chunk_size)\n            logger.info(f\"Enqueueing file end: {path.name} reads: {total_reads}\")\n            queues.enqueue_data(path, total_reads)\n\n        except Exception as exc:\n            import traceback\n\n            logger.error(f\"Enqueueing exception: {path.name} {exc}\")\n            queues.enqueue_exception(path, exc, traceback.format_exc())\n\n    logger.info(\"Enqueue sentinel\")\n    queues.enqueue_data(None, None)\n\n\n@logged(log_args=True)\ndef handle_exception(\n    exception: Tuple[Path, Exception, str],\n    output_handler: OutputHandler,\n    status: StatusMonitor,\n    strict: bool,\n) -> None:\n    path, exc, trace = exception\n    status.write(str(exc), sys.stderr)\n    output_handler.set_input_complete(path, is_exception=True)\n\n    if strict:\n        status.close()\n        logger.fatal(\"Exception raised and --strict set\")\n        logger.debug(f\"trace: {trace}\")\n        raise exc\n\n\n@logged_all\ndef process_conversion_tasks(\n    queues: QueueManager,\n    output_handler: OutputHandler,\n    status: StatusMonitor,\n    strict: bool,\n    threads: int = DEFAULT_THREADS,\n) -> None:\n    \"\"\"Work through the queues of data until all work is done\"\"\"\n\n    count_complete_processes = 0\n    while count_complete_processes < threads:\n        # Always poll exceptions to ensure they're handled\n        exception = queues.get_exception()\n        if exception is not None:\n            handle_exception(\n                exception=exception,\n                output_handler=output_handler,\n                status=status,\n                strict=strict,\n            )\n            continue\n\n        path, data = queues.await_data()\n\n        # Handle exhausted processes\n        if path is None:\n            # Processed finished sentinel\n            count_complete_processes += 1\n            logger.info(\n                f\"Got process end sentinel {count_complete_processes} of {threads}\"\n            )\n            continue\n\n        # Update the progress bar with the total number of converted reads in the file\n        if isinstance(data, int):\n            status.update_reads_total(path, data)\n            output_handler.set_input_complete(path, is_exception=False)\n            continue\n\n        # Write the incoming list of converted reads\n        writer = output_handler.get_writer(path)\n        if writer is None:\n            logger.warn(\n                f\"Trying to write to {path} writer which was closed by an exception\"\n            )\n        else:\n            logger.info(f\"Writing {len(data)} reads to {path.name} using {writer}\")\n            writer.add_reads(data)\n            status.increment_reads(len(data))\n\n    status.close()\n\n\n@logged(log_time=True)\ndef convert_from_fast5(\n    inputs: List[Path],\n    output: Path,\n    recursive: bool = False,\n    threads: int = DEFAULT_THREADS,\n    one_to_one: Optional[Path] = None,\n    force_overwrite: bool = False,\n    signal_chunk_size: int = DEFAULT_SIGNAL_CHUNK_SIZE,\n    strict: bool = False,\n) -> None:\n    \"\"\"\n    Convert fast5 files found (optionally recursively) at the given input Paths\n    into pod5 file(s). If one_to_one is a Path then the new pod5 files are\n    created in a new relative directory structure within output relative to the the\n    one_to_one Path.\n    \"\"\"\n\n    if output.is_file() and not force_overwrite:\n        raise FileExistsError(\n            \"Output path points to an existing file and --force-overwrite not set\"\n        )\n\n    if len(output.parts) > 1:\n        output.parent.mkdir(parents=True, exist_ok=True)\n\n    threads = limit_threads(threads)\n\n    pending_fast5s = collect_inputs(inputs, recursive, \"*.fast5\", threads=threads)\n    if not pending_fast5s:\n        logger.fatal(f\"Found no *.fast5 files in inputs: {inputs}\")\n        raise RuntimeError(\"Found no fast5 inputs to process - Exiting\")\n\n    output_handler = OutputHandler(output, one_to_one, force_overwrite)\n    status = StatusMonitor(pending_fast5s)\n\n    threads = min(threads, len(pending_fast5s))\n    ctx = mp.get_context(\"spawn\")\n    queues = QueueManager(\n        context=ctx,\n        inputs=pending_fast5s,\n        threads=threads,\n        timeout=TIMEOUT_SECONDS,\n    )\n\n    active_processes = []\n    for _ in range(threads):\n        process = ctx.Process(\n            target=convert_fast5_files,\n            args=(queues, signal_chunk_size),\n            daemon=True,\n        )\n        process.start()\n        active_processes.append(process)\n\n    try:\n        process_conversion_tasks(\n            queues=queues,\n            output_handler=output_handler,\n            status=status,\n            strict=strict,\n            threads=threads,\n        )\n\n        queues.shutdown()\n        for proc in active_processes:\n            proc.join()\n            proc.close()\n\n    except Exception as exc:\n        status.write(f\"An unexpected error occurred: {exc}\", file=sys.stderr)\n        terminate_processes(active_processes)\n        raise exc\n\n    finally:\n        output_handler.close_all()\n        logger.disabled = True\n\n\ndef main():\n    \"\"\"Main function for pod5_convert_from_fast5\"\"\"\n    run_tool(pod5_convert_from_fast5_argparser())\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/pod5_convert_to_fast5.py",
    "content": "\"\"\"\nTool for converting pod5 files to the legacy fast5 format\n\"\"\"\n\nimport time\nfrom concurrent.futures import Future, ProcessPoolExecutor, as_completed\nfrom pathlib import Path\nfrom typing import Dict, List, Tuple\n\nimport h5py\nimport numpy\nimport vbz_h5py_plugin  # noqa: F401\nfrom more_itertools import chunked\n\nimport pod5 as p5\nfrom pod5.tools.parsers import pod5_convert_to_fast5_argparser, run_tool\nfrom pod5.tools.utils import DEFAULT_THREADS, collect_inputs, limit_threads\n\n# Pod5 does not have 'partial' so need to add that back in here.\nFAST5_END_REASONS = {\n    \"unknown\": 0,\n    \"partial\": 1,  # Do not remove, required by fast5.\n    \"mux_change\": 2,\n    \"unblock_mux_change\": 3,\n    \"data_service_unblock_mux_change\": 4,\n    \"signal_positive\": 5,\n    \"signal_negative\": 6,\n    \"api_request\": 7,\n    \"device_data_error\": 8,\n    \"analysis_config_change\": 9,\n    \"paused\": 10,\n}\n\n# Fast5 types\nFAST5_END_REASON_TYPE = h5py.enum_dtype(FAST5_END_REASONS)\nFAST5_STRING_TYPE = h5py.string_dtype(\"ascii\")\n\n\nclass StatusMonitor:\n    \"\"\"Class for monitoring the status / progress of the conversion\"\"\"\n\n    def __init__(self, file_count: int):\n        self.update_interval = 10\n\n        self.file_count = file_count\n        self.files_started = 0\n        self.files_ended = 0\n        self.read_count = 0\n        self.reads_processed = 0\n        self.sample_count = 0\n\n        self.time_start = self.time_last_update = time.time()\n\n    @property\n    def running(self) -> bool:\n        \"\"\"Return true if not all files have finished processing\"\"\"\n        return self.files_ended < self.file_count\n\n    def increment(\n        self,\n        *,\n        files_started: int = 0,\n        files_ended: int = 0,\n        read_count: int = 0,\n        reads_processed: int = 0,\n        sample_count: int = 0,\n    ) -> None:\n        \"\"\"Incremeent the status counters\"\"\"\n        self.files_started += files_started\n        self.files_ended += files_ended\n        self.read_count += read_count\n        self.reads_processed += reads_processed\n        self.sample_count += sample_count\n\n    @property\n    def samples_mb(self) -> float:\n        \"\"\"Return the samples count in megabytes\"\"\"\n        return (self.sample_count * 2) / 1_000_000\n\n    @property\n    def time_elapsed(self) -> float:\n        \"\"\"Return the total time elapsed in seconds\"\"\"\n        return self.time_last_update - self.time_start\n\n    @property\n    def sample_rate(self) -> float:\n        \"\"\"Return the time averaged sample rate\"\"\"\n        return self.samples_mb / self.time_elapsed\n\n    def print_status(self, force: bool = False):\n        \"\"\"Print the status if the update interval has passed or if forced\"\"\"\n        now = time.time()\n\n        if force or self.time_last_update + self.update_interval < now:\n            self.time_last_update = now\n\n            print(\n                f\"{self.reads_processed} reads,\\t\",\n                f\"{self.formatted_sample_count},\\t\",\n                f\"{self.files_ended}/{self.file_count} files,\\t\",\n                f\"{self.sample_rate:.1f} MB/s\",\n            )\n\n    @property\n    def formatted_sample_count(self) -> str:\n        \"\"\"Return the sample count as a string with leading Metric prefix if necessary\"\"\"\n        units = [\n            (1000000000000, \"T\"),\n            (1000000000, \"G\"),\n            (1000000, \"M\"),\n            (1000, \"K\"),\n        ]\n\n        for div, unit in units:\n            if self.sample_count > div:\n                return f\"{self.sample_count/div:.1f} {unit}Samples\"\n        return f\"{self.sample_count} Samples\"\n\n\ndef write_pod5_record_to_fast5(read: p5.ReadRecord, fast5: h5py.File) -> None:\n    tracking_id = read.run_info.tracking_id\n\n    read_group = fast5.create_group(f\"read_{read.read_id}\")\n    read_group.attrs.create(\n        \"run_id\",\n        tracking_id[\"run_id\"].encode(\"ascii\"),\n        dtype=FAST5_STRING_TYPE,\n    )\n    read_group.attrs.create(\n        \"pore_type\",\n        read.pore.pore_type.encode(\"ascii\"),\n        dtype=FAST5_STRING_TYPE,\n    )\n\n    tracking_id_group = read_group.create_group(\"tracking_id\")\n    for k, v in tracking_id.items():\n        tracking_id_group.attrs[k] = v\n\n    context_tags_group = read_group.create_group(\"context_tags\")\n    for k, v in read.run_info.context_tags.items():\n        context_tags_group.attrs[k] = v\n\n    channel_id_group = read_group.create_group(\"channel_id\")\n    digitisation = read.run_info.adc_max - read.run_info.adc_min + 1\n    channel_id_group.attrs.create(\"digitisation\", digitisation, dtype=numpy.float64)\n    channel_id_group.attrs.create(\n        \"offset\", read.calibration.offset, dtype=numpy.float64\n    )\n\n    channel_id_group.attrs.create(\n        \"range\", digitisation * read.calibration.scale, dtype=numpy.float64\n    )\n    channel_id_group.attrs.create(\n        \"sampling_rate\", read.run_info.sample_rate, dtype=numpy.float64\n    )\n    channel_id_group.attrs[\"channel_number\"] = str(read.pore.channel)\n\n    raw_group = read_group.create_group(\"Raw\")\n    raw_group.create_dataset(\n        \"Signal\",\n        data=read.signal,\n        dtype=numpy.int16,\n        compression=32020,\n        compression_opts=(0, 2, 1, 1),\n    )\n    raw_group.attrs.create(\"start_time\", read.start_sample, dtype=numpy.uint64)\n    raw_group.attrs.create(\"duration\", read.sample_count, dtype=numpy.uint32)\n    raw_group.attrs.create(\"read_number\", read.read_number, dtype=numpy.int32)\n    raw_group.attrs.create(\"start_mux\", read.pore.well, dtype=numpy.uint8)\n    raw_group.attrs[\"read_id\"] = str(read.read_id).encode(\"utf-8\")\n    raw_group.attrs.create(\"median_before\", read.median_before, dtype=numpy.float64)\n\n    # Lookup the fast5 enumeration values, which should include \"partial: 1\"\n    # This will ensure that the enumeration is valid on a round-trip\n    raw_group.attrs.create(\n        \"end_reason\",\n        FAST5_END_REASONS[read.end_reason.name],\n        dtype=FAST5_END_REASON_TYPE,\n    )\n\n    raw_group.attrs.create(\n        \"num_minknow_events\", read.num_minknow_events, dtype=numpy.uint64\n    )\n\n    raw_group.attrs.create(\n        \"tracked_scaling_scale\",\n        read.tracked_scaling.scale,\n        dtype=numpy.float32,\n    )\n    raw_group.attrs.create(\n        \"tracked_scaling_shift\",\n        read.tracked_scaling.shift,\n        dtype=numpy.float32,\n    )\n    raw_group.attrs.create(\n        \"predicted_scaling_scale\",\n        read.predicted_scaling.scale,\n        dtype=numpy.float32,\n    )\n    raw_group.attrs.create(\n        \"predicted_scaling_shift\",\n        read.predicted_scaling.shift,\n        dtype=numpy.float32,\n    )\n    raw_group.attrs.create(\n        \"num_reads_since_mux_change\",\n        read.num_reads_since_mux_change,\n        dtype=numpy.uint32,\n    )\n    raw_group.attrs.create(\n        \"time_since_mux_change\",\n        read.time_since_mux_change,\n        dtype=numpy.float32,\n    )\n    # Note dropping the 'open_pore_level' attribute as it is not supported in fast5\n\n\ndef convert_pod5_to_fast5(\n    source: Path, dest: Path, read_ids: List[str]\n) -> Tuple[int, int]:\n    \"\"\"\n    Open a source pod5 file and write the selected read_ids into the destination fast5\n    file target.\n    \"\"\"\n\n    dest.parent.mkdir(parents=True, exist_ok=True)\n\n    if dest.exists():\n        dest.unlink()\n\n    total_samples = 0\n\n    with p5.Reader(source) as reader:\n        with h5py.File(dest, \"w\") as f5:\n            f5.attrs.create(\n                \"file_version\", \"3.0\".encode(\"ascii\"), dtype=FAST5_STRING_TYPE\n            )\n            f5.attrs.create(\n                \"file_type\", \"multi-read\".encode(\"ascii\"), dtype=FAST5_STRING_TYPE\n            )\n\n            # Take the chunk of read ids for this file\n            for read in reader.reads(\n                selection=read_ids, missing_ok=False, preload={\"samples\"}\n            ):\n                write_pod5_record_to_fast5(read, f5)\n\n                total_samples += read.num_samples\n\n    return (len(read_ids), total_samples)\n\n\ndef convert_to_fast5(\n    inputs: List[Path],\n    output: Path,\n    recursive: bool = False,\n    threads: int = DEFAULT_THREADS,\n    force_overwrite: bool = False,\n    file_read_count: int = 4000,\n):\n    if output.exists() and not output.is_dir():\n        raise FileExistsError(\"Cannot output to a file\")\n\n    threads = limit_threads(threads)\n\n    with ProcessPoolExecutor(max_workers=threads) as executor:\n        total_reads = 0\n        futures: Dict[Future, Path] = {}\n\n        # Enumerate over input pod5 files\n        for input_idx, source in enumerate(\n            collect_inputs(inputs, recursive, \"*.pod5\", threads=threads)\n        ):\n            # Open the inputs to read the read ids\n            with p5.Reader(source) as reader:\n                for chunk_idx, read_ids in enumerate(\n                    chunked(reader.read_ids, file_read_count)\n                ):\n                    dest = (\n                        output / f\"{source.stem}.{chunk_idx}_{input_idx}.fast5\"\n                    ).resolve()\n\n                    if dest.exists() and not force_overwrite:\n                        raise FileExistsError(\n                            \"Output path points to an existing file and --force-overwrite not set\"\n                        )\n\n                    kwargs = {\n                        \"source\": source,\n                        \"dest\": dest,\n                        \"read_ids\": read_ids,\n                    }\n                    futures[executor.submit(convert_pod5_to_fast5, **kwargs)] = dest  # type: ignore\n\n                total_reads += len(reader.read_ids)\n\n        print(f\"Converting pod5s into {len(futures)} fast5 files. Please wait...\")\n\n        status = StatusMonitor(file_count=len(inputs))\n        status.increment(files_started=len(inputs), read_count=total_reads)\n\n        for idx, future in enumerate(as_completed(futures)):\n            (reads_converted, samples_converted) = future.result()\n\n            status.increment(\n                files_ended=1,\n                sample_count=samples_converted,\n                reads_processed=reads_converted,\n            )\n            status.print_status()\n\n        status.print_status(force=True)\n\n        print(\"Conversion complete\")\n\n\ndef main():\n    run_tool(pod5_convert_to_fast5_argparser())\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/pod5_filter.py",
    "content": "\"\"\"\nTool for subsetting pod5 files into one or more outputs using a list of read ids\n\"\"\"\n\nfrom pathlib import Path\nfrom typing import List\n\nimport lib_pod5 as p5b\nimport polars as pl\n\nfrom pod5.tools.parsers import prepare_pod5_filter_argparser, run_tool\nfrom pod5.tools.pod5_subset import build_targets_dict\nfrom pod5.tools.polars_utils import PL_DEST_FNAME, PL_READ_ID, PL_UUID_REGEX\nfrom pod5.tools.utils import (\n    DEFAULT_THREADS,\n    collect_inputs,\n    init_logging,\n    limit_threads,\n    logged_all,\n)\n\nlogger = init_logging()\n\npl.enable_string_cache()\n\n\n@logged_all\ndef parse_read_id_targets(ids: Path, output: Path) -> pl.LazyFrame:\n    \"\"\"Parse the list of read_ids checking all are valid uuids\"\"\"\n    read_ids = (\n        pl.scan_csv(\n            ids,\n            has_header=False,  # Any header will be filtered out by is_uuid\n            comment_prefix=\"#\",\n            new_columns=[PL_READ_ID],\n            rechunk=False,\n        )\n        .drop_nulls()\n        .unique()\n        .with_columns(\n            [\n                pl.lit(str(output.resolve())).cast(pl.Categorical).alias(PL_DEST_FNAME),\n                pl.col(PL_READ_ID).str.contains(PL_UUID_REGEX).alias(\"is_uuid\"),\n            ]\n        )\n        .filter(pl.col(\"is_uuid\"))\n        .drop(\"is_uuid\")\n    )\n\n    if len(read_ids.fetch(10)) == 0:\n        raise AssertionError(f\"Found 0 read_ids in {ids}. Nothing to do\")\n\n    return read_ids\n\n\n@logged_all\ndef filter_pod5(\n    inputs: List[Path],\n    output: Path,\n    ids: Path,\n    missing_ok: bool = False,\n    force_overwrite: bool = False,\n    recursive: bool = False,\n    threads: int = DEFAULT_THREADS,\n) -> int:\n    \"\"\"Prepare the pod5 filter mapping and run the repacker\"\"\"\n    # Remove output file\n    if output.exists():\n        if not force_overwrite:\n            raise FileExistsError(\n                f\"Output file already exists and --force-overwrite not set - {output}\"\n            )\n        else:\n            output.unlink()\n\n    # Create parent directories if they do not exist\n    if not output.parent.exists():\n        output.parent.mkdir(parents=True, exist_ok=True)\n\n    targets = parse_read_id_targets(ids, output=output)\n    print(f\"Parsed {len(targets.collect())} reads_ids from: {ids.name}\")\n\n    threads = limit_threads(threads)\n\n    _inputs = collect_inputs(inputs, recursive, \"*.pod5\", threads=threads)\n\n    targets_dict = build_targets_dict(targets)\n\n    try:\n        p5b.subset_pod5s_with_mapping(\n            list(_inputs),\n            output,\n            targets_dict,\n            # threads=threads,\n            missing_ok,\n            False,\n            force_overwrite,\n        )\n    except KeyboardInterrupt:\n        print(\"Stopped POD5 filter following keyboard interrupt.\")\n        return 1\n\n    return 0\n\n\n@logged_all\ndef main():\n    \"\"\"pod5 filter main\"\"\"\n    run_tool(prepare_pod5_filter_argparser())\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/pod5_inspect.py",
    "content": "\"\"\"\nTool for inspecting the contents of pod5 files\n\"\"\"\n\nimport csv\nimport os\nimport sys\nfrom dataclasses import asdict\nfrom pathlib import Path\nfrom typing import Callable, Dict, List\nfrom uuid import UUID\n\nimport pod5 as p5\nfrom pod5.tools.parsers import prepare_pod5_inspect_argparser, run_tool\nfrom pod5.tools.utils import collect_inputs\n\n\ndef format_shift_scale_pair(pair):\n    return f\"({pair.shift} {pair.scale})\"\n\n\ndef format_shift_scale_pair_num(pair):\n    return f\"({pair.shift:.1f} {pair.scale:.1f})\"\n\n\ndef do_reads_command(reader: p5.Reader, write_header: bool):\n    keys = [\n        \"read_id\",\n        \"channel\",\n        \"well\",\n        \"pore_type\",\n        \"read_number\",\n        \"start_sample\",\n        \"end_reason\",\n        \"median_before\",\n        \"num_samples\",\n        \"byte_count\",\n        \"signal_compression_ratio\",\n        \"num_minknow_events\",\n        \"tracked_scaling\",\n        \"predicted_scaling\",\n        \"num_reads_since_mux_change\",\n        \"time_since_mux_change\",\n        \"open_pore_level\",\n    ]\n\n    csv_read_writer = csv.DictWriter(sys.stdout, keys)\n\n    # Only write header on first call\n    if write_header:\n        csv_read_writer.writeheader()\n\n    for read in reader.reads():\n        fields = {\n            \"read_id\": read.read_id,\n            \"channel\": read.pore.channel,\n            \"well\": read.pore.well,\n            \"pore_type\": read.pore.pore_type,\n            \"read_number\": read.read_number,\n            \"start_sample\": read.start_sample,\n            \"end_reason\": read.end_reason.name,\n            \"median_before\": f\"{read.median_before:.1f}\",\n            \"num_samples\": read.num_samples,\n            \"byte_count\": read.byte_count,\n            \"signal_compression_ratio\": f\"{read.byte_count / float(read.sample_count*2):.3f}\",\n            \"num_minknow_events\": read.num_minknow_events,\n            \"tracked_scaling\": format_shift_scale_pair_num(read.tracked_scaling),\n            \"predicted_scaling\": format_shift_scale_pair_num(read.predicted_scaling),\n            \"num_reads_since_mux_change\": read.num_reads_since_mux_change,\n            \"time_since_mux_change\": read.time_since_mux_change,\n            \"open_pore_level\": read.open_pore_level,\n        }\n\n        try:\n            csv_read_writer.writerow(fields)\n        except BrokenPipeError:\n            devnull = os.open(os.devnull, os.O_WRONLY)\n            os.dup2(devnull, sys.stdout.fileno())\n            break\n\n\ndef dump_run_info(run_info: p5.RunInfo):\n    tab = \"\\t\"\n    for name, value in asdict(run_info).items():\n        if isinstance(value, list):\n            print(f\"{tab}{name}:\")\n            for k, v in value:\n                print(f\"{tab*2}{k}: {v}\")\n        else:\n            print(f\"{tab}{name}: {value}\")\n\n\ndef do_read_command(reader: p5.Reader, read_id: str, **_):\n    try:\n        uuid_read_id = UUID(read_id)\n\n    except ValueError:\n        print(f\"Supplied read_id '{read_id}' is not a valid UUID\", file=sys.stderr)\n        return\n\n    for read in reader.reads():\n        if read.read_id != uuid_read_id:\n            continue\n\n        print(f\"read_id: {read.read_id}\")\n        print(f\"read_number:\\t{read.read_number}\")\n        print(f\"start_sample:\\t{read.start_sample}\")\n        print(f\"median_before:\\t{read.median_before}\")\n        print(f\"open_pore_level:\\t{read.open_pore_level}\")\n        print(\"channel data:\")\n        print(f\"\\tchannel: {read.pore.channel}\")\n        print(f\"\\twell: {read.pore.well}\")\n        print(f\"\\tpore_type: {read.pore.pore_type}\")\n        print(\"end reason:\")\n        print(f\"\\tname: {read.end_reason.name}\")\n        print(f\"\\tforced: {read.end_reason.forced}\")\n        print(\"calibration:\")\n        print(f\"\\toffset: {read.calibration.offset}\")\n        print(f\"\\tscale: {read.calibration.scale}\")\n        print(\"samples:\")\n        print(f\"\\tsample_count: {read.sample_count}\")\n        print(f\"\\tbyte_count: {read.byte_count}\")\n        print(\n            f\"\\tcompression ratio: {read.byte_count / float(read.sample_count*2):.3f}\"\n        )\n\n        print(\"run info:\")\n        dump_run_info(read.run_info)\n        break\n\n\ndef do_debug_command(reader: p5.Reader, **_):\n    batch_count = 0\n    batch_sizes = []\n    read_count = 0\n    sample_count = 0\n    byte_count = 0\n    min_sample = float(\"inf\")\n    max_sample = 0\n\n    run_infos = {}\n\n    for batch in reader.read_batches():\n        batch_count += 1\n\n        batch_read_count = 0\n        for read in batch.reads():\n            batch_read_count += 1\n            read_sample_count = read.sample_count\n            sample_count += read_sample_count\n            byte_count += read.byte_count\n\n            run_info_index = read.run_info_index\n            if run_info_index not in run_infos:\n                run_infos[run_info_index] = read.run_info\n\n            min_sample = min(min_sample, read.start_sample)\n            max_sample = max(max_sample, read.start_sample + read_sample_count)\n        batch_sizes.append(batch_read_count)\n        read_count += batch_read_count\n\n    print(f\"Contains {read_count} reads, in {batch_count} batches: {batch_sizes}\")\n    print(f\"Reads span from sample {min_sample} to {max_sample}\")\n    print(\n        f\"{sample_count} samples, {byte_count}\"\n        f\" bytes: {100*byte_count/float(sample_count*2):.1f} % signal compression ratio\"\n    )\n\n    for idx, run_info in run_infos.items():\n        print(f\"Run info {idx}:\")\n        dump_run_info(run_info)\n\n\ndef do_summary_command(reader: p5.Reader, **kwargs):\n    batch_count = 0\n    total_read_count = 0\n\n    print(\n        f\"Originating file version {reader.file_version}, in memory read table version {reader.reads_table_version}.\"\n    )\n    print(f\"File version on disk {reader.file_version_pre_migration}.\")\n    if reader.is_vbz_compressed:\n        print(\"File uses VBZ compression.\")\n    else:\n        print(\"File is uncompressed.\")\n\n    for batch in reader.read_batches():\n        batch_count += 1\n\n        batch_read_count = 0\n        for _ in batch.reads():\n            batch_read_count += 1\n\n        print(f\"Batch {batch_count}, {batch_read_count} reads\")\n        total_read_count += batch_read_count\n    print(f\"Found {batch_count} batches, {total_read_count} reads\")\n\n\ndef inspect_pod5(\n    command: str, input_files: List[Path], recursive: bool = False, **kwargs\n):\n    \"\"\"Determine which inspect command to run from the parsed arguments and run it\"\"\"\n\n    commands: Dict[str, Callable] = {\n        \"reads\": do_reads_command,\n        \"read\": do_read_command,\n        \"summary\": do_summary_command,\n        \"debug\": do_debug_command,\n    }\n\n    for idx, filename in enumerate(\n        collect_inputs(input_files, recursive=recursive, pattern=\"*.pod5\")\n    ):\n        try:\n            reader = p5.Reader(filename)\n        except Exception as exc:\n            print(f\"Failed to open pod5 file: {filename}: {exc}\", file=sys.stderr)\n            continue\n\n        kwargs[\"reader\"] = reader\n        kwargs[\"write_header\"] = idx == 0\n        commands[command](**kwargs)\n\n\ndef main():\n    \"\"\"Run the pod5 inspect tool\"\"\"\n    run_tool(prepare_pod5_inspect_argparser())\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/pod5_merge.py",
    "content": "\"\"\"\nTool for merging pod5 files\n\"\"\"\n\nfrom time import sleep\nfrom typing import Iterable\nfrom pathlib import Path\nfrom tqdm.auto import tqdm\n\nimport pod5 as p5\nimport pod5.repack as p5_repack\nfrom pod5.tools.parsers import prepare_pod5_merge_argparser, run_tool\nfrom pod5.tools.utils import (\n    DEFAULT_THREADS,\n    PBAR_DEFAULTS,\n    collect_inputs,\n    init_logging,\n    logged_all,\n)\n\nlogger = init_logging()\n\n\n@logged_all\ndef merge_pod5(\n    inputs: Iterable[Path],\n    output: Path,\n    force_overwrite: bool = False,\n    recursive: bool = False,\n    threads: int = DEFAULT_THREADS,\n    readers: int = 5,\n) -> None:\n    \"\"\"\n    Merge the an iterable of input pod5 paths into the specified output path\n    \"\"\"\n\n    if output.exists():\n        if force_overwrite:\n            output.unlink()\n        else:\n            raise FileExistsError(\n                f\"Output files already exists and --force-overwrite not set. \"\n                f\"Refusing to overwrite {output}.\"\n            )\n\n    if not output.parent.exists():\n        output.parent.mkdir(parents=True, exist_ok=True)\n\n    _inputs = collect_inputs(\n        inputs, recursive=recursive, pattern=\"*.pod5\", threads=threads\n    )\n\n    print(f\"Merging reads from {len(_inputs)} files\")\n    logger.debug(f\"Merging reads from {len(_inputs)} files into {output.absolute()}\")\n\n    # Open the output file writer\n    with p5.Writer(output.absolute()) as writer:\n        # Attach the writer to the repacker\n        repacker = p5_repack.Repacker()\n        repacker_output = repacker.add_output(writer, True)\n\n        pbar = tqdm(\n            total=len(_inputs),\n            desc=\"Merging\",\n            unit=\"File\",\n            leave=True,\n            position=0,\n            **PBAR_DEFAULTS,\n        )\n\n        active_limit = max(readers, 1)\n        logger.debug(f\"{active_limit=}\")\n\n        opened_readers = 0\n        active = 0\n        while _inputs or active > 0:\n            pbar.update(opened_readers - active - pbar.n)\n\n            active = repacker.currently_open_file_reader_count\n            if _inputs and (active < active_limit):\n                next_input = _inputs.pop()\n                logger.debug(f\"submitting: {next_input=}\")\n                with p5.Reader(next_input) as reader:\n                    opened_readers += 1\n                    repacker.add_all_reads_to_output(repacker_output, reader)\n                    continue\n\n            if not _inputs:\n                logger.debug(\"no inputs remaining - finishing\")\n                repacker.set_output_finished(repacker_output)\n                break\n\n            sleep(0.2)\n            logger.debug(f\"{len(_inputs)=}, {active=}, {(active > 0)=}\")\n\n        repacker.finish()\n        del repacker\n        pbar.update(opened_readers - active - pbar.n)\n        pbar.close()\n\n    return\n\n\ndef main():\n    \"\"\"pod5_merge main program\"\"\"\n    run_tool(prepare_pod5_merge_argparser())\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/pod5_recover.py",
    "content": "\"\"\"\nTool for recovering truncated pod5 files\n\"\"\"\n\nimport dataclasses\nimport typing\nfrom contextlib import suppress\nfrom pathlib import Path\n\nimport lib_pod5 as p5b\nimport pod5 as p5\nfrom pod5.tools.parsers import prepare_pod5_recover_argparser, run_tool\nfrom pod5.tools.utils import collect_inputs\n\n\n@dataclasses.dataclass\nclass RecoveredData:\n    \"\"\"\n    Holds info about recovered data.\n    \"\"\"\n\n    signal_rows: int = 0\n    reads: int = 0\n    run_infos: int = 0\n    files_with_errors: int = 0\n\n\ndef is_file_ok(path: Path) -> bool:\n    try:\n        with p5.Reader(path):\n            pass\n        return True\n    except RuntimeError:\n        return False\n\n\ndef recover_pod5(\n    inputs: typing.List[Path], force_overwrite: bool, recursive: bool, cleanup: bool\n):\n    \"\"\"\n    Given a list of truncated pod5 files, recover their data.\n    \"\"\"\n\n    paths = collect_inputs(inputs, recursive=recursive, pattern=[\"*.tmp\", \"*.pod5\"])\n\n    paths_to_recover = [p for p in paths if not is_file_ok(p)]\n\n    if len(paths_to_recover) == 0:\n        print(f\"None of the {len(paths)} files given need recovery\")\n        return\n\n    for path in paths_to_recover:\n        dest = path.parent / (path.stem + \"_recovered.pod5\")\n        if dest.exists():\n            if force_overwrite:\n                dest.unlink()\n            else:\n                raise FileExistsError(\n                    f\"Output files already exists and --force-overwrite not set. \"\n                    f\"Refusing to overwrite {dest}.\"\n                )\n\n    recovered_data = RecoveredData()\n    options = p5b.RecoverFileOptions()\n    options.cleanup = cleanup\n    for input_file in paths_to_recover:\n        dest = input_file.parent / (input_file.stem + \"_recovered.pod5\")\n        try:\n            details = p5b.recover_file(\n                str(input_file.resolve()), str(dest.resolve()), options\n            )\n            recovered_data.signal_rows += details.row_counts.signal\n            recovered_data.run_infos += details.row_counts.run_info\n            recovered_data.reads += details.row_counts.reads\n            print(f\"{dest} - Recovered\")\n            for cleanup_error in details.cleanup_errors:\n                print(\n                    \"Warning cleanup failed to cleanup file \"\n                    + f\"'{cleanup_error.file_path}' due to : {cleanup_error.description}\"\n                )\n        except RuntimeError as error:\n            recovered_data.files_with_errors += 1\n            print(f\"{dest} - Recovery failed - {str(error)}\")\n            with suppress(FileNotFoundError):\n                dest.unlink()\n\n    print(\n        f\"Recovered {recovered_data.signal_rows} signal rows, \"\n        f\"{recovered_data.reads} reads, \"\n        f\"{recovered_data.run_infos} run infos, \"\n        f\"{recovered_data.files_with_errors} files with errors\"\n    )\n\n\ndef main():\n    run_tool(prepare_pod5_recover_argparser())\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/pod5_repack.py",
    "content": "\"\"\"\nTool for repacking pod5 files to potentially improve performance\n\"\"\"\n\nfrom concurrent.futures import ProcessPoolExecutor, as_completed\nimport typing\nfrom pathlib import Path\nfrom tqdm.auto import tqdm\n\nimport pod5 as p5\nimport pod5.repack\nfrom pod5.tools.utils import (\n    DEFAULT_THREADS,\n    PBAR_DEFAULTS,\n    assert_no_duplicate_filenames,\n    collect_inputs,\n    limit_threads,\n)\nfrom pod5.tools.parsers import prepare_pod5_repack_argparser, run_tool\n\n\ndef resolve_overwrite(src: Path, dest: Path, force: bool) -> None:\n    if dest.exists():\n        if dest == src:\n            raise FileExistsError(f\"Refusing to overwrite {src} inplace\")\n        if force:\n            dest.unlink()\n        else:\n            raise FileExistsError(\n                \"Refusing to overwrite output without --force-overwrite\"\n            )\n\n\ndef repack_pod5_file(src: Path, dest: Path):\n    \"\"\"Repack the source pod5 file into dest\"\"\"\n    repacker = pod5.repack.Repacker()\n    with p5.Writer(dest) as writer:\n        repacker_output = repacker.add_output(writer, False)\n        with p5.Reader(src) as reader:\n            # Add all reads to the repacker\n            repacker.add_all_reads_to_output(repacker_output, reader)\n        repacker.set_output_finished(repacker_output)\n        repacker.finish()\n\n\ndef repack_pod5(\n    inputs: typing.List[Path],\n    output: Path,\n    threads: int = DEFAULT_THREADS,\n    force_overwrite: bool = False,\n    recursive: bool = False,\n):\n    \"\"\"Given a list of pod5 files, repack their contents and write files 1-1\"\"\"\n\n    if output.exists() and not output.is_dir():\n        raise ValueError(f\"Output cannot be an existing file: {output}\")\n\n    # Create output directory if required\n    if not output.is_dir():\n        output.mkdir(parents=True, exist_ok=True)\n\n    threads = limit_threads(threads)\n\n    _inputs = collect_inputs(\n        inputs, recursive=recursive, pattern=\"*.pod5\", threads=threads\n    )\n    assert_no_duplicate_filenames(_inputs)\n\n    # Remove existing files if required\n    for input_filename in _inputs:\n        output_filename = output / input_filename.name\n        resolve_overwrite(input_filename, output_filename, force_overwrite)\n\n    futures = {}\n    with ProcessPoolExecutor(max_workers=threads) as executor:\n        pbar = tqdm(total=len(_inputs), unit=\"Files\", **PBAR_DEFAULTS)\n\n        for src in _inputs:\n            dest = output / src.name\n            futures[executor.submit(repack_pod5_file, src=src, dest=dest)] = dest\n\n        for future in as_completed(futures):\n            tqdm.write(f\"Finished {futures[future]}\")\n            pbar.update(1)\n\n    pbar.close()\n    print(\"Done\")\n\n\ndef main():\n    run_tool(prepare_pod5_repack_argparser())\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/pod5_subset.py",
    "content": "\"\"\"\nTool for subsetting pod5 files into one or more outputs\n\"\"\"\n\nfrom collections import defaultdict\nfrom copy import deepcopy\nfrom pathlib import Path\nfrom string import Formatter\nfrom typing import List, Optional, Tuple\n\nimport lib_pod5 as p5b\nimport polars as pl\n\nfrom pod5.tools.parsers import prepare_pod5_subset_argparser, run_tool\nfrom pod5.tools.polars_utils import (\n    PL_DEST_FNAME,\n    PL_READ_ID,\n    PL_UUID_REGEX,\n)\nfrom pod5.tools.utils import (\n    DEFAULT_THREADS,\n    collect_inputs,\n    init_logging,\n    logged,\n    logged_all,\n)\n\nDEFAULT_READ_ID_COLUMN = \"read_id\"\n\nlogger = init_logging()\n\npl.enable_string_cache()\n\n\n@logged_all\ndef get_separator(path: Path) -> str:\n    \"\"\"\n    Inspect the first line of the file at path and attempt to determine the field\n    separator as either tab or comma, depending on the number of occurrences of each\n    Returns \",\" or \"<tab>\"\n    \"\"\"\n    with path.open(\"r\") as fh:\n        line = fh.readline()\n    n_tabs = line.count(\"\\t\")\n    n_comma = line.count(\",\")\n    if n_tabs >= n_comma:\n        return \"\\t\"\n    return \",\"\n\n\n@logged_all\ndef default_filename_template(subset_columns: List[str]) -> str:\n    \"\"\"Create the default filename template from the subset_columns selected\"\"\"\n    default = \"_\".join(f\"{col}-{{{col}}}\" for col in subset_columns)\n    default += \".pod5\"\n    return default\n\n\n@logged_all\ndef column_keys_from_template(template: str) -> List[str]:\n    \"\"\"Get a list of placeholder keys in the template\"\"\"\n    return [key for _, key, _, _ in Formatter().parse(template) if key]\n\n\n@logged_all\ndef fstring_to_polars(\n    template: str,\n) -> Tuple[str, List[str]]:\n    \"\"\"\n    Replace f-string keyed placeholders with positional ones and return the keys in\n    their respective position\n    \"\"\"\n    # This is for pl.format positional syntax\n    replaced = template\n    keys = column_keys_from_template(template)\n    for key in keys:\n        replaced = replaced.replace(f\"{{{key}}}\", \"{}\")\n    return replaced, keys\n\n\n@logged_all\ndef parse_table_mapping(\n    summary_path: Path,\n    filename_template: Optional[str],\n    subset_columns: List[str],\n    read_id_column: str = DEFAULT_READ_ID_COLUMN,\n    ignore_incomplete_template: bool = False,\n) -> pl.LazyFrame:\n    \"\"\"\n    Parse a table using polars to create a mapping of output targets to read ids\n    \"\"\"\n    if not subset_columns:\n        raise AssertionError(\"Missing --columns when using --summary / --table\")\n\n    if not filename_template:\n        filename_template = default_filename_template(subset_columns)\n\n    assert_filename_template(\n        filename_template, subset_columns, ignore_incomplete_template\n    )\n\n    # Add the destination filename as a column\n    pl_template, keys = fstring_to_polars(filename_template)\n\n    columns = deepcopy(subset_columns)\n    columns.append(read_id_column)\n\n    targets = (\n        pl.read_csv(\n            summary_path,\n            columns=columns,\n            separator=get_separator(summary_path),\n            comment_prefix=\"#\",\n        )\n        .lazy()\n        .with_columns(\n            [\n                pl.format(pl_template, *keys).cast(pl.Categorical).alias(PL_DEST_FNAME),\n                pl.col(read_id_column).alias(PL_READ_ID),\n            ]\n        )\n        .with_columns(\n            [\n                pl.col(PL_READ_ID).str.contains(PL_UUID_REGEX).alias(\"is_uuid\"),\n            ]\n        )\n        .filter(pl.col(\"is_uuid\"))\n        .drop(\"is_uuid\")\n    )\n    return targets\n\n\n@logged_all\ndef assert_filename_template(\n    template: str, subset_columns: List[str], ignore_incomplete_template: bool\n) -> None:\n    \"\"\"\n    Get the keys named in the template to assert that they exist in subset_columns\n    \"\"\"\n    # Parse the template string to get the keywords\n    # \"{hello}_world_{name}\" -> [\"hello\", \"name\"]\n    template_keys = set(args[1] for args in Formatter().parse(template) if args[1])\n    allowed_keys = set(subset_columns)\n\n    # Assert there are no unexpected keys in the template\n    unexpected = template_keys - allowed_keys\n    if unexpected:\n        raise KeyError(f\"--template {template} has unexpected keys: {unexpected}\")\n\n    # Assert there are no unused keys in the template\n    # This is important as the output would be degenerate on some keys\n    if not ignore_incomplete_template:\n        unused = allowed_keys - template_keys\n        if unused:\n            raise KeyError(\n                f\"--template {template} does not use {unused} keys. \"\n                \"Use --ignore-incomplete-template to suppress this exception.\"\n            )\n\n\n@logged_all\ndef create_default_filename_template(subset_columns: List[str]) -> str:\n    \"\"\"Create the default filename template from the subset_columns selected\"\"\"\n    default = \"_\".join(f\"{col}-{{{col}}}\" for col in subset_columns)\n    default += \".pod5\"\n    return default\n\n\n@logged_all\ndef parse_csv_mapping(csv_path: Path) -> pl.LazyFrame:\n    \"\"\"Parse the csv direct mapping of output target to read_ids to a targets dataframe\"\"\"\n    targets = (\n        pl.scan_csv(\n            csv_path,\n            has_header=False,\n            comment_prefix=\"#\",\n            new_columns=[PL_DEST_FNAME, PL_READ_ID],\n            rechunk=False,\n        )\n        .drop_nulls()\n        .with_columns(\n            [\n                pl.col(PL_DEST_FNAME).cast(pl.Categorical),\n                pl.col(PL_READ_ID).str.contains(PL_UUID_REGEX).alias(\"is_uuid\"),\n            ]\n        )\n        .filter(pl.col(\"is_uuid\"))\n        .drop(\"is_uuid\")\n    )\n\n    if len(targets.fetch(10)) == 0:\n        raise AssertionError(f\"Found 0 read_ids in {csv_path}. Nothing to do\")\n\n    return targets\n\n\n@logged(log_time=True)\ndef build_targets_dict(\n    targets: pl.LazyFrame,\n) -> dict[str, list[str]]:\n    \"\"\"Build a dictionary of output filename to read_ids from the targets dataframe\"\"\"\n    targets_dict = defaultdict(set)\n    for row in targets.select([PL_READ_ID, PL_DEST_FNAME]).collect().iter_rows():\n        read_id, fname = row\n        targets_dict[fname].add(read_id)\n\n    return {k: list(v) for k, v in targets_dict.items()}\n\n\n@logged(log_time=True)\ndef subset_pod5(\n    inputs: List[Path],\n    output: Path,\n    columns: List[str],\n    csv: Optional[Path] = None,\n    table: Optional[Path] = None,\n    threads: int = DEFAULT_THREADS,\n    template: str = \"\",\n    read_id_column: str = DEFAULT_READ_ID_COLUMN,\n    missing_ok: bool = False,\n    ignore_incomplete_template: bool = False,\n    force_overwrite: bool = False,\n    recursive: bool = False,\n) -> int:\n    \"\"\"Prepare the subsampling mapping and run the repacker\"\"\"\n\n    if csv:\n        targets = parse_csv_mapping(csv)\n\n    elif table:\n        targets = parse_table_mapping(\n            table, template, columns, read_id_column, ignore_incomplete_template\n        )\n\n    else:\n        raise RuntimeError(\n            \"Arguments provided could not be used to generate a subset mapping.\"\n        )\n\n    targets_dict = build_targets_dict(targets)\n\n    if not output.exists():\n        output.mkdir(parents=True)\n\n    _inputs = collect_inputs(\n        inputs, recursive=recursive, pattern=\"*.pod5\", threads=threads\n    )\n    if len(_inputs) == 0:\n        raise ValueError(\"Found no input pod5 files\")\n\n    try:\n        p5b.subset_pod5s_with_mapping(\n            list(_inputs),\n            output,\n            targets_dict,\n            # threads=threads,\n            missing_ok,\n            False,\n            force_overwrite,\n        )\n    except KeyboardInterrupt:\n        print(\"Stopped POD5 subset following keyboard interrupt.\")\n        return 1\n    return 0\n\n\n@logged()\ndef main():\n    \"\"\"pod5 subsample main\"\"\"\n    run_tool(prepare_pod5_subset_argparser())\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/pod5_update.py",
    "content": "\"\"\"\nTool for updating pod5 files to the latest available version\n\"\"\"\n\nfrom typing import Iterable\nfrom pathlib import Path\n\nfrom tqdm.auto import tqdm\n\nimport lib_pod5 as p5b\n\nimport pod5 as p5\nfrom pod5.tools.parsers import prepare_pod5_update_argparser, run_tool\nfrom pod5.tools.utils import (\n    PBAR_DEFAULTS,\n    assert_no_duplicate_filenames,\n    collect_inputs,\n)\n\n\ndef update_pod5(\n    inputs: Iterable[Path],\n    output: Path,\n    force_overwrite: bool = False,\n    recursive: bool = False,\n):\n    \"\"\"\n    Given a list of pod5 files, update their tables to the most recent version\n    \"\"\"\n    if not output.exists():\n        output.mkdir(parents=True, exist_ok=True)\n\n    paths = collect_inputs(inputs, recursive=recursive, pattern=\"*.pod5\")\n    assert_no_duplicate_filenames(paths)\n\n    exists = set(output / p.name for p in paths if Path(output / p.name).exists())\n\n    if not paths.isdisjoint(exists):\n        inout = [p.name for p in exists - paths]\n        raise AssertionError(f\"Cannot update inputs in-place. Found: {inout}\")\n\n    if not force_overwrite and exists:\n        raise FileExistsError(\n            f\"{len(exists)} Output files already exists and --force-overwrite not set. \"\n            f\"Found: {exists}\"\n        )\n    else:\n        for path in exists:\n            path.unlink()\n\n    pbar = tqdm(\n        total=len(paths), desc=\"Updating\", unit=\"File\", leave=True, **PBAR_DEFAULTS\n    )\n\n    for path in paths:\n        dest = output / path.name\n        with p5.Reader(path) as reader:\n            p5b.update_file(reader.inner_file_reader, str(dest))\n        pbar.update()\n\n\ndef main():\n    run_tool(prepare_pod5_update_argparser())\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/pod5_view.py",
    "content": "import codecs\nimport multiprocessing as mp\nfrom multiprocessing.context import SpawnProcess\nfrom multiprocessing.synchronize import Lock\nimport os\nfrom pathlib import Path\nfrom queue import Empty\nimport sys\nfrom typing import Callable, Dict, Generator, List, NamedTuple, Optional, Set, Tuple\n\nfrom pod5.reader import ArrowTableHandle\nimport polars as pl\nimport pyarrow as pa\n\nimport pod5 as p5\nfrom pod5.tools.parsers import prepare_pod5_view_argparser, run_tool\nfrom pod5.tools.polars_utils import (\n    pl_format_empty_string,\n    pl_format_read_id,\n    pl_from_arrow,\n    pl_from_arrow_batch,\n)\nfrom pod5.tools.utils import (\n    DEFAULT_THREADS,\n    collect_inputs,\n    init_logging,\n    limit_threads,\n    logged,\n    logged_all,\n    terminate_processes,\n)\n\n\nlogger = init_logging()\n\npl.enable_string_cache()\n\n\nclass Selection(NamedTuple):\n    selected: Set[str]  # The set of column names selected\n    reads_fields: Set[str]  # The set of read table fields required\n    info_fields: Set[str]  # The set of run info table fields required\n\n    def __contains__(self, key):\n        return key in self.selected\n\n    def union(self) -> Set[str]:\n        return self.reads_fields.union(self.info_fields)\n\n\nclass Field(NamedTuple):\n    \"\"\"Container class for storing the expression for a named field\"\"\"\n\n    docs: str\n    reads_fields: Optional[List[str]] = None\n    info_fields: Optional[List[str]] = None\n\n\n# This dict defines the order of the fields\nFIELDS: Dict[str, Field] = {\n    \"read_id\": Field(\n        \"Read UUID\",\n        [\"read_id\"],\n    ),\n    \"filename\": Field(\n        \"Source pod5 filename\",\n    ),\n    \"read_number\": Field(\n        \"Read number\",\n        [\"read_number\"],\n    ),\n    \"channel\": Field(\n        \"1-indexed channel\",\n        [\"channel\"],\n    ),\n    \"mux\": Field(\n        \"1-indexed well\",\n        [\"well\"],\n    ),\n    \"end_reason\": Field(\n        \"End reason string\",\n        [\"end_reason\"],\n    ),\n    \"start_time\": Field(\n        \"Seconds since the run start to the first sample of this read\",\n        [\"start\"],\n        [\"sample_rate\"],\n    ),\n    \"start_sample\": Field(\n        \"Samples recorded on this channel since run start to the first sample of this read\",\n        [\"start\"],\n    ),\n    \"duration\": Field(\n        \"Seconds of sampling for this read\",\n        [\"num_samples\", \"sample_rate\"],\n    ),\n    \"num_samples\": Field(\n        \"Number of signal samples\",\n        [\"num_samples\"],\n    ),\n    \"minknow_events\": Field(\n        \"Number of minknow events that this read contains\",\n        [\"num_minknow_events\"],\n    ),\n    \"sample_rate\": Field(\n        \"Number of samples recorded each second\",\n        [\"sample_rate\"],\n    ),\n    \"median_before\": Field(\n        \"Current level in this well before the read\",\n        [\"median_before\"],\n    ),\n    # DEPRECATED\n    \"predicted_scaling_scale\": Field(\n        \"Scale for predicted read scaling\",\n        [\"predicted_scaling_scale\"],\n    ),\n    # DEPRECATED\n    \"predicted_scaling_shift\": Field(\n        \"Shift for predicted read scaling\",\n        [\"predicted_scaling_shift\"],\n    ),\n    # DEPRECATED\n    \"tracked_scaling_scale\": Field(\n        \"Scale for tracked read scaling\",\n        [\"tracked_scaling_scale\"],\n    ),\n    # DEPRECATED\n    \"tracked_scaling_shift\": Field(\n        \"Shift for tracked read scaling\",\n        [\"tracked_scaling_shift\"],\n    ),\n    \"num_reads_since_mux_change\": Field(\n        \"Number of selected reads since the last mux change on this channel\",\n        [\"num_reads_since_mux_change\"],\n    ),\n    \"time_since_mux_change\": Field(\n        \"Seconds since the last mux change on this channel\",\n        [\"time_since_mux_change\"],\n    ),\n    \"run_id\": Field(\"Run UUID\", None, [\"protocol_run_id\"]),\n    \"sample_id\": Field(\n        \"User-supplied name for the sample\",\n        None,\n        [\"sample_id\"],\n    ),\n    \"experiment_id\": Field(\n        \"User-supplied name for the experiment\",\n        None,\n        [\"experiment_name\"],\n    ),\n    \"flow_cell_id\": Field(\n        \"The flow cell id\",\n        None,\n        [\"flow_cell_id\"],\n    ),\n    \"pore_type\": Field(\n        \"Name of the pore in this well\",\n        [\"pore_type\"],\n    ),\n    \"open_pore_level\": Field(\n        \"The tracked open pore level for this read\",\n        [\"open_pore_level\"],\n    ),\n}\n\n\n@logged()\ndef print_fields():\n    \"\"\"Print a list of the available columns\"\"\"\n    for name, field in FIELDS.items():\n        print(f\"{name.ljust(28)} {field.docs}\")\n    print(\"\")\n\n\n@logged_all\ndef get_field_or_raise(key: str) -> Field:\n    \"\"\"Get the Field for this key or raise a KeyError\"\"\"\n    try:\n        return FIELDS[key]\n    except KeyError:\n        raise KeyError(\n            f\"Field: '{key}' did not match any known fields. \"\n            \"Please check it exists by viewing `-L/--list-fields`\"\n        )\n\n\n@logged_all\ndef select_fields(\n    *,\n    group_read_id: bool = False,\n    include: Optional[str] = None,\n    exclude: Optional[str] = None,\n) -> Selection:\n    \"\"\"Select fields to write\"\"\"\n    selected: Set[str] = set([])\n\n    # Select only read ids\n    if group_read_id:\n        selected.add(\"read_id\")\n        return Selection(selected, selected, set())\n\n    if include:\n        for key in include.split(\",\"):\n            key = key.strip()\n            if not key:\n                continue\n            get_field_or_raise(key)\n            selected.add(key)\n\n    # Default selection - All fields\n    if not selected:\n        selected.update(FIELDS.keys())\n\n    if exclude:\n        for key in exclude.split(\",\"):\n            key = key.strip()\n            if not key:\n                continue\n            get_field_or_raise(key)\n            try:\n                selected.remove(key)\n            except KeyError:\n                pass\n\n    if not selected:\n        raise RuntimeError(\"Zero Fields selected. Please select at least one field\")\n\n    reads_fields: Set[str] = set()\n    info_fields: Set[str] = set()\n\n    for field_name in selected:\n        field = FIELDS[field_name]\n        if field.reads_fields:\n            reads_fields.update(field.reads_fields)\n        if field.info_fields:\n            info_fields.update(field.info_fields)\n\n    # If we use the anything from run_info - add fields to perform the join\n    if info_fields:\n        reads_fields.update([\"run_info\"])\n        info_fields.update([\"acquisition_id\"])\n\n    return Selection(selected, reads_fields, info_fields)\n\n\ndef get_format_view_table_fn(\n    path: Path, selection: Selection\n) -> Callable[[pl.LazyFrame], pl.LazyFrame]:\n    \"\"\"Format the view table based on the selected fields\"\"\"\n    drop: Set[str] = set()\n    exprs: List[pl.Expr] = []\n    if \"filename\" in selection:\n        exprs.append(pl.lit(path.name).alias(\"filename\"))\n    if \"read_id\" in selection:\n        exprs.append(pl_format_read_id(pl.col(\"read_id\")))\n    if \"mux\" in selection:\n        exprs.append(pl.col(\"well\").alias(\"mux\"))\n    if \"start_time\" in selection:\n        exprs.append((pl.col(\"start\") / pl.col(\"sample_rate\")).alias(\"start_time\"))\n        if \"start\" not in selection:\n            drop.add(\"start\")\n        if \"sample_rate\" not in selection:\n            drop.add(\"sample_rate\")\n    if \"start_sample\" in selection:\n        exprs.append(pl.col(\"start\").alias(\"start_sample\"))\n    if \"duration\" in selection:\n        exprs.append((pl.col(\"num_samples\") / pl.col(\"sample_rate\")).alias(\"duration\"))\n        if \"num_samples\" not in selection:\n            drop.add(\"num_samples\")\n        if \"sample_rate\" not in selection:\n            drop.add(\"sample_rate\")\n    if \"minknow_events\" in selection:\n        exprs.append(pl.col(\"num_minknow_events\").alias(\"minknow_events\"))\n    if \"run_id\" in selection:\n        exprs.append(pl.col(\"protocol_run_id\").alias(\"run_id\"))\n    if \"experiment_id\" in selection:\n        exprs.append(pl.col(\"experiment_name\").alias(\"experiment_id\"))\n\n    maybe_empty = [\"experiment_id\", \"protocol_run_id\", \"sample_id\", \"flow_cell_id\"]\n    order = [key for key in FIELDS.keys() if key in selection.selected]\n\n    # All tables are the same so we can compute this work ONCE\n    def format_view_table(lf: pl.LazyFrame) -> pl.LazyFrame:\n        lf = lf.with_columns(exprs)\n\n        # Replace potentially empty fields with \"not_set\"\n        # This can't be done in the above expression due to the behaviour of\n        # name.keep()\n        empty_cols = [f for f in maybe_empty if f in lf.collect_schema().names()]\n        if empty_cols:\n            lf = lf.with_columns(\n                pl_format_empty_string(pl.col(empty_cols), \"not_set\").name.keep()\n            )\n\n        # Apply the field selection order\n        return lf.select(order)\n\n    return format_view_table\n\n\n@logged(log_time=True)\ndef write(\n    ldf: pl.LazyFrame,\n    output: Optional[Path],\n    separator: str = \"\\t\",\n) -> None:\n    \"\"\"Write the polars.LazyFrame\"\"\"\n\n    kwargs = dict(\n        include_header=False, separator=separator, null_value=\"\", float_precision=8\n    )\n\n    # Write to the nominated output path\n    if output is not None:\n        with output.open(\"ab\") as f:\n            ldf.collect().write_csv(f, **kwargs)\n        return\n\n    # No output path, collect the table content as a string and print it to stdout\n    content = ldf.collect().write_csv(**kwargs)\n    try:\n        # Do not add additional newline at the end, this ensures consistency with\n        # writing to file\n        print(content, end=\"\")\n    except BrokenPipeError as exc:\n        # https://docs.python.org/3/library/signal.html#note-on-sigpipe\n        devnull = os.open(os.devnull, os.O_WRONLY)\n        os.dup2(devnull, sys.stdout.fileno())\n        raise exc\n\n\ndef write_header(\n    output: Optional[Path], selection: Selection, separator: str = \"\\t\"\n) -> None:\n    \"\"\"Write the header line\"\"\"\n    header = separator.join(key for key in FIELDS if key in selection.selected)\n    if output is None:\n        print(header, file=sys.stdout, flush=True)\n    else:\n        output.write_text(header + \"\\n\")\n\n\n@logged_all\ndef resolve_output(output: Optional[Path], force_overwrite: bool) -> Optional[Path]:\n    \"\"\"\n    Resolve the output path if necessary checking for no accidental overwrite\n    and resolving to default output if given a path\n    \"\"\"\n    if output is None:\n        return None\n\n    # Do not allow accidental overwrite\n    if output.is_file():\n        if not force_overwrite:\n            raise FileExistsError(\n                f\"{output} points to an existing file and --force-overwrite not set\"\n            )\n        output.unlink()\n\n    # If given a directory, check the default filename is valid\n    if output.is_dir():\n        default_name = output / \"view.txt\"\n        return resolve_output(default_name, force_overwrite)\n\n    return output\n\n\n@logged()\ndef assert_unique_acquisition_id(run_info: pl.LazyFrame, path: Path) -> None:\n    \"\"\"\n    Perform a check that the acquisition ids are unique raising AssertionError otherwise\n    \"\"\"\n    groups = run_info.collect().group_by(pl.col(\"acquisition_id\"))\n    common_acq_ids = [acq_id for acq_id, frame in groups if frame.n_unique() != 1]\n    if common_acq_ids:\n        raise AssertionError(\n            f\"Found non-unique run_info acquisition_id in {path.name}: {common_acq_ids}. \"\n        )\n\n\ndef parse_reads_table_all(\n    reader: p5.Reader, included_fields: List[int]\n) -> pl.LazyFrame:\n    \"\"\"\n    Parse all records in the reads table returning a polars LazyFrame\n    \"\"\"\n    logger.debug(f\"Parsing {reader.path.name} records {included_fields=}\")\n\n    options = pa.ipc.IpcReadOptions(included_fields=included_fields)\n    with ArrowTableHandle(\n        reader.inner_file_reader.get_file_read_table_location(), options=options\n    ) as handle:\n        reads_table = handle.reader.read_all()\n        reads_table = pl_from_arrow(reads_table, rechunk=False).lazy()\n\n    return reads_table\n\n\ndef parse_reads_table_batch(\n    reader: p5.Reader, included_fields: List[int], batch_index: int\n) -> Tuple[pl.LazyFrame, int]:\n    \"\"\"\n    Parse the reads table record batch at `batch_index` from a pod5 file returning a\n    polars LazyFrame and the number of records in it\n    \"\"\"\n    logger.debug(\n        f\"Parsing {reader.path.name} record batch {batch_index} {included_fields=}\"\n    )\n\n    options = pa.ipc.IpcReadOptions(included_fields=included_fields)\n    with ArrowTableHandle(\n        reader.inner_file_reader.get_file_read_table_location(), options=options\n    ) as handle:\n        reads_batch = handle.reader.get_record_batch(batch_index)\n        num_reads = reads_batch.num_rows\n        reads_batch = pl_from_arrow_batch(reads_batch, rechunk=False).lazy()\n\n    return reads_batch, num_reads\n\n\n@logged_all\ndef parse_read_table_chunks(\n    reader: p5.Reader, included_fields: List[int], approx_size: int = 99_999\n) -> Generator[pl.LazyFrame, None, None]:\n    \"\"\"\n    Read record batches and yield polars lazyframes of `approx_size` records.\n    Records are yielded in units of whole batches of the underlying table\n    \"\"\"\n    chunks: List[pl.LazyFrame] = []\n    chunk_rows = 0\n\n    for batch_index in range(reader.read_table.num_record_batches):\n        reads, n_rows = parse_reads_table_batch(reader, included_fields, batch_index)\n\n        chunks.append(reads)\n        chunk_rows += n_rows\n\n        if chunk_rows > approx_size:\n            reads_chunk = pl.concat(chunks)\n            logger.debug(f\"Emitting chunk of {chunk_rows} rows\")\n            chunks = []\n            chunk_rows = 0\n            yield reads_chunk\n\n    if chunk_rows > 0:\n        reads_chunk = pl.concat(chunks)\n        chunks = []\n        logger.debug(f\"Emitting final chunk of {chunk_rows} rows\")\n        yield reads_chunk\n\n\n@logged()\ndef parse_run_info_table(\n    reader: p5.Reader, selection: Selection\n) -> Optional[pl.LazyFrame]:\n    \"\"\"Parse the reads table from a pod5 file returning a polars LazyFrame\"\"\"\n    included_fields: List[int] = []\n    for field_idx, name in enumerate(reader.run_info_table.schema.names):\n        if name in selection.info_fields:\n            included_fields.append(field_idx)\n\n    if not included_fields:\n        return None\n\n    options = pa.ipc.IpcReadOptions(included_fields=included_fields)\n\n    with ArrowTableHandle(\n        reader.inner_file_reader.get_file_run_info_table_location(), options=options\n    ) as handle:\n        table = handle.reader.read_all()\n        table = pl_from_arrow(table, rechunk=False).lazy()\n\n    assert_unique_acquisition_id(table, reader.path)\n    return table\n\n\n@logged()\ndef join_reads_to_run_info(reads: pl.LazyFrame, run_info: pl.LazyFrame) -> pl.LazyFrame:\n    \"\"\"Join the reads and run_info tables\"\"\"\n    return reads.with_columns(pl.col(\"run_info\").cast(pl.Utf8)).join(\n        run_info.unique(),\n        left_on=\"run_info\",\n        right_on=\"acquisition_id\",\n    )\n\n\ndef get_included_reads_table_fields(reader: p5.Reader, selection: Selection):\n    included_fields: List[int] = []\n    for field_idx, name in enumerate(reader.read_table.schema.names):\n        if name in selection.reads_fields:\n            included_fields.append(field_idx)\n\n    if not included_fields:\n        raise KeyError(\n            f\"No reads fields set in {selection.selected=} {selection.reads_fields=}\"\n        )\n    return included_fields\n\n\ndef get_reads_tables(\n    path: Path, selection: Selection, threshold: int = 100_000\n) -> Generator[pl.LazyFrame, None, None]:\n    \"\"\"\n    Generate lazy dataframes from pod5 records. If the number of records\n    is greater than `threshold` then yield chunks to limit memory consumption and\n    improve overall performance\n    \"\"\"\n\n    with p5.Reader(path) as reader:\n        included_fields = get_included_reads_table_fields(reader, selection)\n\n        format_view_table_fn: Callable[[pl.LazyFrame], pl.LazyFrame] = (\n            get_format_view_table_fn(path, selection)\n        )\n\n        run_info = parse_run_info_table(reader, selection)\n\n        if reader.num_reads <= threshold:\n            reads_table = parse_reads_table_all(reader, included_fields)\n            if run_info is not None:\n                reads_table = join_reads_to_run_info(reads_table, run_info)\n\n            yield format_view_table_fn(reads_table)\n            return\n\n        for reads_chunk in parse_read_table_chunks(\n            reader, included_fields, approx_size=threshold - 1\n        ):\n            if run_info is not None:\n                reads_chunk = join_reads_to_run_info(reads_chunk, run_info)\n            yield format_view_table_fn(reads_chunk)\n\n\ndef join_workers(processes: List[SpawnProcess], exceptions: mp.JoinableQueue) -> None:\n    \"\"\"Poll workers checking for exceptions which will likely cause\"\"\"\n    prcs = {p for p in processes}\n    while prcs:\n        try:\n            exc, path = exceptions.get(timeout=0.1)\n            terminate_processes(processes)\n            exceptions.task_done()\n            if isinstance(exc, BrokenPipeError):\n                sys.exit(1)\n            else:\n                terminate_processes(processes)\n                raise RuntimeError(f\"Error while processing '{path}'\") from exc\n        except Empty:\n            pass\n\n        done = set()\n        for prc in prcs:\n            exit_code = prc.exitcode\n\n            if exit_code is None:\n                continue\n\n            if exit_code > 0:\n                terminate_processes(processes)\n                raise mp.ProcessError(\n                    f\"Unexpected exception ocurrecd in {prc} - exit code: {exit_code}\"\n                )\n            else:\n                done.add(prc)\n        prcs.difference_update(done)\n\n    for prc in processes:\n        prc.join()\n\n\n@logged_all\ndef worker_process(\n    paths: mp.JoinableQueue,\n    exceptions: mp.JoinableQueue,\n    lock: Lock,\n    output: Path,\n    separator: bool,\n    selection: Selection,\n) -> None:\n    \"\"\"\n    Consume pod5 paths from `paths` queue, parse the records and write to `output` after\n    acquiring `lock`.\n    Returns `None` when all finish sentinel `None` is received in `paths` queue.\n    \"\"\"\n    path: Optional[Path] = None\n    try:\n        while True:\n            path = paths.get()\n            if path is None:\n                paths.task_done()\n                break\n\n            try:\n                for table in get_reads_tables(path, selection):\n                    with lock:\n                        write(ldf=table, output=output, separator=separator)\n            finally:\n                paths.task_done()\n        paths.close()\n\n    except Exception as exc:\n        exceptions.put((exc, path))\n\n\ndef launch_view_workers(\n    paths: Set[Path],\n    output: Path,\n    selection: Selection,\n    separator: str,\n    num_workers: int,\n):\n    ctx = mp.get_context(\"spawn\")\n    write_lock = ctx.Lock()\n    paths_queue = ctx.JoinableQueue(maxsize=len(paths) * 2)\n    exceptions_queue = ctx.JoinableQueue(maxsize=len(paths))\n\n    # Prepare the paths queue\n    for path in paths:\n        paths_queue.put(path)\n\n    processes: List[SpawnProcess] = []\n    for _ in range(num_workers):\n        worker = ctx.Process(\n            target=worker_process,\n            kwargs=dict(\n                paths=paths_queue,\n                exceptions=exceptions_queue,\n                lock=write_lock,\n                output=output,\n                separator=separator,\n                selection=selection,\n            ),\n            daemon=True,\n        )\n        worker.start()\n        processes.append(worker)\n\n        # Enqueue a stop sentinel for each worker\n        paths_queue.put(None)\n\n    join_workers(processes, exceptions_queue)\n\n    paths_queue.join()\n    paths_queue.close()\n    paths_queue.join_thread()\n\n\n@logged_all\ndef view_pod5(\n    inputs: List[Path],\n    output: Path,\n    separator: str = \"\\t\",\n    recursive: bool = False,\n    force_overwrite: bool = False,\n    list_fields: bool = False,\n    no_header: bool = False,\n    threads: int = DEFAULT_THREADS,\n    **kwargs,\n) -> None:\n    \"\"\"Given a list of POD5 files write a table to view their contents\"\"\"\n\n    if list_fields:\n        print_fields()\n        return\n\n    threads = limit_threads(threads)\n\n    output_path = resolve_output(output, force_overwrite)\n\n    # Decode escaped separator characters e.g. \\t\n    sep = codecs.decode(separator, \"unicode-escape\")\n\n    # Parse column selection args\n    selection = select_fields(**kwargs)\n\n    collected_paths = collect_inputs(\n        inputs, recursive=recursive, pattern=\"*.pod5\", threads=threads\n    )\n    if not collected_paths:\n        raise AssertionError(\"Found no pod5 files searching inputs\")\n\n    num_workers = min(len(collected_paths), threads)\n\n    if not no_header:\n        write_header(output=output_path, selection=selection, separator=sep)\n\n    launch_view_workers(\n        paths=collected_paths,\n        output=output_path,\n        selection=selection,\n        separator=sep,\n        num_workers=num_workers,\n    )\n\n\ndef main():\n    run_tool(prepare_pod5_view_argparser())\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/polars_utils.py",
    "content": "from typing import Optional\nimport polars as pl\nimport pyarrow as pa\n\n# Reserved column names used in polars dataframes\nPL_DEST_FNAME = \"__dest_fname\"\nPL_SRC_FNAME = \"__src_fname\"\nPL_READ_ID = \"__read_id\"\nPL_UUID_REGEX = \"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$\"\n\n\ndef pl_format_read_id(read_id_col: pl.Expr) -> pl.Expr:\n    \"\"\"Format read ids to in UUID style\"\"\"\n    read_id = read_id_col.bin.encode(\"hex\")\n    return pl.format(\n        \"{}-{}-{}-{}-{}\",\n        read_id.str.slice(0, 8),\n        read_id.str.slice(8, 4),\n        read_id.str.slice(12, 4),\n        read_id.str.slice(16, 4),\n        read_id.str.slice(20, 12),\n    )\n\n\ndef pl_format_empty_string(expr: pl.Expr, subst: Optional[str]) -> pl.Expr:\n    \"\"\"Empty strings are read as a pair of double-quotes which need to be removed\"\"\"\n    return pl.when(expr.str.len_bytes() == 0).then(pl.lit(subst)).otherwise(expr)\n\n\ndef pl_from_arrow(table: pa.Table, rechunk: bool) -> pl.DataFrame:\n    \"\"\"Workaround failure to read our arrow extension type\"\"\"\n    # Based on https://github.com/pola-rs/polars/issues/20700\n\n    def remove_pod5_metadata(field: pa.Field) -> pa.Field:\n        metadata = field.metadata\n        if metadata is not None and metadata.get(b\"ARROW:extension:name\") in [\n            b\"minknow.uuid\",\n            b\"minknow.vbz\",\n        ]:\n            del metadata[b\"ARROW:extension:name\"]\n            field = field.remove_metadata().with_metadata(metadata)\n        return field\n\n    table = pa.Table.from_batches(\n        table.to_batches(),\n        schema=pa.schema([remove_pod5_metadata(field) for field in table.schema]),\n    )\n    return pl.from_arrow(table, rechunk=rechunk)\n\n\ndef pl_from_arrow_batch(record_batch: pa.RecordBatch, rechunk: bool) -> pl.DataFrame:\n    \"\"\"Workaround failure to read our arrow extension type\"\"\"\n    table = pa.Table.from_batches([record_batch])\n    return pl_from_arrow(table, rechunk=rechunk)\n"
  },
  {
    "path": "python/pod5/src/pod5/tools/utils.py",
    "content": "\"\"\"\nUtility functions for pod5 tools\n\"\"\"\n\nimport datetime\nimport functools\nimport glob\nimport logging\nimport multiprocessing as mp\nfrom multiprocessing.context import SpawnProcess\nimport os\nfrom time import perf_counter\nfrom typing import Collection, Iterable, List, Set, Union\nfrom pathlib import Path\nimport uuid\n\n\n# os.cpu_count() can return None if it fails\nDEFAULT_THREADS = min(os.cpu_count() or 4, 4)\n\n\ndef init_logging():\n    \"\"\"Initialise logging only if POD5_DEBUG is true\"\"\"\n    if not is_pod5_debug():\n        logger = logging.getLogger(\"pod5\")\n        logger.addHandler(logging.NullHandler())\n        return logger\n\n    datetime_now = datetime.datetime.now().strftime(\"%Y-%m-%d--%H-%M-%S\")\n    if mp.current_process().name == \"MainProcess\":\n        pid = \"main\"\n    else:\n        pid = f\"p-{os.getpid()}\"\n\n    logger = logging.getLogger(\"pod5\")\n    logger.setLevel(logging.DEBUG)\n    file_handler = logging.FileHandler(filename=f\"{datetime_now}-{pid}-pod5.log\")\n    file_handler.setFormatter(\n        logging.Formatter(\"%(asctime)s %(levelname)s %(message)s\")\n    )\n    file_handler.setLevel(logging.DEBUG)\n    logger.addHandler(file_handler)\n    return logger\n\n\ndef logged(log_return: bool = False, log_args: bool = False, log_time: bool = False):\n    \"\"\"Logging parameterised decorator\"\"\"\n\n    def decorator(func):\n        @functools.wraps(func)\n        def wrapper(*args, **kwargs):\n            logger = logging.getLogger(\"pod5\")\n            uid = f\"{str(uuid.uuid4())[:2]}:'{func.__name__}'\"\n            if log_args:\n                logger.debug(\"{0}:{1}, {2}\".format(uid, args, kwargs))\n            else:\n                logger.debug(\"{0}\".format(uid))\n            try:\n                started = perf_counter()\n                ret = func(*args, **kwargs)\n            except Exception as exc:\n                logger.debug(\"{0}:Exception:{1}\".format(uid, exc))\n                raise exc\n            if log_time:\n                duration_s = perf_counter() - started\n                logger.debug(\"{0}:Done:{1:.3f}s\".format(uid, duration_s))\n            if log_return:\n                logger.debug(\"{0}:Returned:{1}\".format(uid, ret))\n            return ret\n\n        return wrapper\n\n    return decorator\n\n\nlogged_all = logged(log_return=True, log_args=True, log_time=True)\n\n\n@logged_all\ndef terminate_processes(processes: List[SpawnProcess]) -> None:\n    \"\"\"terminate all child processes\"\"\"\n    for proc in processes:\n        try:\n            proc.terminate()\n        except ValueError:\n            # Catch ValueError raised if proc is already closed\n            pass\n    return\n\n\n@logged(log_return=True)\ndef limit_threads(requested: int) -> int:\n    \"\"\"\n    Santise and limit the number of ``requested`` threads to the number of logical cores\n    \"\"\"\n    if requested < 1:\n        return os.cpu_count() or 4\n    return min(os.cpu_count() or requested, requested)\n\n\n@logged(log_time=True)\ndef collect_inputs(\n    paths: Iterable[Path],\n    recursive: bool,\n    pattern: Union[str, Collection[str]],\n    threads: int = DEFAULT_THREADS,\n) -> Set[Path]:\n    \"\"\"\n    Returns a set of `path` which match any of the given glob-style `pattern`s\n\n    If a path is a directory this will be globbed (optionally recursively).\n    If a path is a file then it must also match any of the given `pattern`s.\n\n    Raises FileExistsError if any inputs do not exist\n    \"\"\"\n    paths = set(paths)\n    assert_inputs_exist(paths)\n    if len(paths) == 0:\n        raise AssertionError(\"Got 0 input paths to search\")\n\n    return search_paths(paths, recursive, pattern, min(threads, len(paths)))\n\n\n@logged(log_time=True, log_return=True)\ndef assert_inputs_exist(inputs: Iterable[Path]):\n    \"\"\"Assert all inputs exist. Raises FileExistsError otherwise\"\"\"\n    bad_paths = set()\n    for path in set(inputs):\n        if not path.exists():\n            bad_paths.add(path)\n\n    if bad_paths:\n        raise FileExistsError(f\"{len(bad_paths)} inputs do not exist: {bad_paths}\")\n\n\n@logged(log_time=True)\ndef search_paths(\n    paths: Iterable[Path],\n    recursive: bool,\n    pattern: Union[str, Collection[str]],\n    threads: int = DEFAULT_THREADS,\n) -> Set[Path]:\n    \"\"\"\n    Search all `paths` matching any of `patterns` searching directories recursively\n    if requested\n    \"\"\"\n    if isinstance(pattern, str):\n        pattern = [pattern]\n\n    srch = functools.partial(search_path, recursive=recursive, patterns=pattern)\n\n    all_matches: Set[Path] = set()\n    with mp.Pool(processes=threads) as pool:\n        for matches in pool.imap_unordered(srch, paths):\n            all_matches.update(matches)\n\n    return all_matches\n\n\n@logged(log_time=True)\ndef search_path(path: Path, recursive: bool, patterns: Collection[str]) -> Set[Path]:\n    \"\"\"\n    Search `path` matching `pattern` searching directories recursively if requested\n    \"\"\"\n\n    def _any_match(path: Path):\n        return any(path.match(p) for p in patterns)\n\n    # Get the recursive or non-recursive glob function.\n    matching_files = set()\n    if path.is_dir():\n        pattern = str(path / \"**\" / \"*\") if recursive else str(path / \"*\")\n        for matching_pathname in glob.glob(pattern, recursive=recursive):\n            matching_path = Path(matching_pathname)\n            if matching_path.is_file() and _any_match(matching_path):\n                matching_files.add(matching_path)\n\n    # Non-directory, assert that it is a file and that it matches the file_pattern\n    elif path.is_file() and _any_match(path):\n        matching_files.add(path)\n\n    return matching_files\n\n\n@logged(log_time=True)\ndef assert_no_duplicate_filenames(inputs: Collection[Path]) -> None:\n    \"\"\"\n    Raises ValueError if there are duplicate filenames in the collection of Paths\n    \"\"\"\n    names = [path.name for path in inputs]\n    if len(names) != len(set(names)):\n        raise ValueError(\n            \"One or more inputs share the same filename. \"\n            \"This would cause a files to be overwritten at runtime\"\n        )\n\n\n# Do not log this function as it's executed at import time\ndef is_disable_pbar() -> bool:\n    \"\"\"Check if POD5_PBAR is set returning true if PBAR should be disabled\"\"\"\n    try:\n        enabled = bool(int(os.environ.get(\"POD5_PBAR\", \"1\")))\n        return not enabled\n    except Exception:\n        return False\n\n\nPBAR_DEFAULTS = dict(\n    disable=is_disable_pbar(),\n    smoothing=0.0,\n    dynamic_ncols=True,\n    ascii=True,\n)\n\n\n# Do not log this function as it's executed during logging initialisation\ndef is_pod5_debug() -> bool:\n    \"\"\"Check if POD5_DEBUG is set\"\"\"\n    try:\n        debug = bool(int(os.environ.get(\"POD5_DEBUG\", \"0\")))\n        return debug\n    except Exception:\n        return True\n"
  },
  {
    "path": "python/pod5/src/pod5/writer.py",
    "content": "\"\"\"\nTools for writing POD5 data\n\"\"\"\n\nimport datetime\nimport itertools\nfrom pathlib import Path\nfrom typing import (\n    Any,\n    Callable,\n    Dict,\n    List,\n    Optional,\n    Sequence,\n    Tuple,\n    Type,\n    TypeVar,\n    Union,\n)\nimport sys\n\nimport lib_pod5 as p5b\nimport numpy as np\nfrom pod5.reader import ReadRecord\nimport pytz\n\nif sys.version_info >= (3, 10):\n    from typing import TypeAlias\nelse:\n    from typing_extensions import TypeAlias\n\nfrom pod5.api_utils import Pod5ApiException, safe_close\nfrom pod5.pod5_types import (\n    BaseRead,\n    CompressedRead,\n    EndReason,\n    PathOrStr,\n    Read,\n    RunInfo,\n)\n\nDEFAULT_SOFTWARE_NAME = \"Python API\"\n\nSignalType: TypeAlias = p5b.SignalType\n\"\"\"The type of compression applied to a signal `SignalType::{UncompressedSignal, VbzSignal}`\"\"\"\nPoreType = str\n\"\"\"The name of a Pore\"\"\"\nT = TypeVar(\"T\", bound=Union[EndReason, PoreType, RunInfo])\n\n\ndef force_type_and_default(value, dtype, count, default_value=None):\n    if default_value is not None and value is None:\n        value = np.array([default_value] * count, dtype=dtype)\n    assert value is not None\n    return value.astype(type, copy=False)\n\n\ndef map_to_tuples(info_map: Any) -> List[Tuple[str, str]]:\n    \"\"\"\n    Convert a fast5 property map (e.g. context_tags and tracking_id) to a\n    tuple or string pairs to pass to pod5 C API\n    \"\"\"\n    if isinstance(info_map, dict):\n        return list((str(key), str(value)) for key, value in info_map.items())\n    if isinstance(info_map, list):\n        return list((str(item[0]), str(item[1])) for item in info_map)\n    raise TypeError(f\"Unknown input type for context tags {type(info_map)}\")\n\n\ndef timestamp_to_int(time_stamp: Union[datetime.datetime, int]) -> int:\n    \"\"\"Convert a datetime timestamp to an integer if it's not already an integer\"\"\"\n    if isinstance(time_stamp, int):\n        return time_stamp\n    return int(time_stamp.astimezone(pytz.utc).timestamp() * 1000)\n\n\nclass Writer:\n    \"\"\"Pod5 File Writer\"\"\"\n\n    def __init__(\n        self,\n        path: PathOrStr,\n        software_name: str = DEFAULT_SOFTWARE_NAME,\n        signal_compression_type: SignalType = SignalType.VbzSignal,\n    ):\n        \"\"\"\n        Open a pod5 file for Writing.\n\n        Parameters\n        ----------\n        path : os.PathLike, str\n            The path to the pod5 file to create\n        software_name : str\n            The name of the application used to create this pod5 file\n        signal_compression_type : SignalType\n            The type of compression to use in the file. Defaults to Vbz.\n        \"\"\"\n        self._path = Path(path).absolute()\n        self._software_name = software_name\n        self._signal_compression_type = signal_compression_type\n\n        if self._path.is_file():\n            raise FileExistsError(\n                f\"Input path already exists. Refusing to overwrite: {self._path}\"\n            )\n\n        options = p5b.FileWriterOptions()\n        options.signal_compression_type = signal_compression_type\n\n        self._writer: Optional[p5b.FileWriter] = p5b.create_file(\n            str(self._path), software_name, options\n        )\n        if not self._writer:\n            raise Pod5ApiException(\n                f\"Failed to open writer at {self._path} : {p5b.get_error_string()}\"\n            )\n\n        self._end_reasons: Dict[EndReason, int] = {}\n        self._pores: Dict[PoreType, int] = {}\n        self._run_infos: Dict[RunInfo, int] = {}\n\n        # Internal lookup of object cache based on their respective type\n        self._index_caches: Dict[Type, Dict[Any, int]] = {\n            EndReason: self._end_reasons,\n            PoreType: self._pores,\n            RunInfo: self._run_infos,\n        }\n\n        # Internal lookup of _add functions based on their respective type\n        self._adder_funcs: Dict[Type, Callable[[Any], int]] = {\n            EndReason: self._add_end_reason,\n            PoreType: self._add_pore_type,\n            RunInfo: self._add_run_info,\n        }\n\n    def __enter__(self) -> \"Writer\":\n        return self\n\n    def __exit__(self, *exc_details) -> None:\n        self.close()\n\n    def close(self) -> None:\n        \"\"\"Close the FileWriter handle\"\"\"\n        safe_close(self, \"_writer\")\n        self._writer = None\n\n    @property\n    def path(self) -> Path:\n        \"\"\"Return the path to the pod5 file\"\"\"\n        return self._path\n\n    @property\n    def software_name(self) -> str:\n        \"\"\"Return the software name used to open this file\"\"\"\n        return self._software_name\n\n    @property\n    def signal_compression_type(self) -> SignalType:\n        \"\"\"Return the signal compression type used by this file\"\"\"\n        return self._signal_compression_type\n\n    def add(self, obj: Union[EndReason, PoreType, RunInfo]) -> int:\n        \"\"\"\n        Add a `EndReason`, `PoreType`, or\n        `RunInfo` object to the Pod5 file (if it doesn't already\n        exist) and return the index of this object in the Pod5 file.\n\n        Parameters\n        ----------\n        obj : Union[EndReason, PoreType, RunInfo]\n            Object to find in this Pod5 file, adding it if it doesn't exist already\n\n        Returns\n        -------\n        index : int\n            The index of the object in the Pod5 file\n        \"\"\"\n        # Get the index cache for the type of object given\n        index_cache = self._index_caches[type(obj)]\n\n        # Return the index of this object if it exists\n        if obj in index_cache:\n            return index_cache[obj]\n\n        # Add object using the associated adder function e.g. _add_pore(pore: Pore)\n        # and store the new index in the cache for future look-ups avoiding duplication\n        added_index = self._adder_funcs[type(obj)](obj)\n        index_cache[obj] = added_index\n\n        # Return the newly added index\n        return added_index\n\n    def _add_end_reason(self, end_reason: EndReason) -> int:\n        \"\"\"Add the given EndReason instance to the pod5 file returning its index\"\"\"\n        if self._writer is None:\n            raise Pod5ApiException(\"Writer handle has been closed\")\n        return self._writer.add_end_reason(end_reason.reason.value)\n\n    def _add_pore_type(self, pore_type: PoreType) -> int:\n        \"\"\"Add the given PoreType instance to the pod5 file returning its index\"\"\"\n        if self._writer is None:\n            raise Pod5ApiException(\"Writer handle has been closed\")\n        return self._writer.add_pore(pore_type)\n\n    def _add_run_info(self, run_info: RunInfo) -> int:\n        \"\"\"Add the given RunInfo instance to the pod5 file returning its index\"\"\"\n        if self._writer is None:\n            raise Pod5ApiException(\"Writer handle has been closed\")\n\n        return self._writer.add_run_info(\n            run_info.acquisition_id,\n            timestamp_to_int(run_info.acquisition_start_time),\n            run_info.adc_max,\n            run_info.adc_min,\n            map_to_tuples(run_info.context_tags),\n            run_info.experiment_name,\n            run_info.flow_cell_id,\n            run_info.flow_cell_product_code,\n            run_info.protocol_name,\n            run_info.protocol_run_id,\n            timestamp_to_int(run_info.protocol_start_time),\n            run_info.sample_id,\n            run_info.sample_rate,\n            run_info.sequencing_kit,\n            run_info.sequencer_position,\n            run_info.sequencer_position_type,\n            run_info.software,\n            run_info.system_name,\n            run_info.system_type,\n            map_to_tuples(run_info.tracking_id),\n        )\n\n    def contains(self, obj: Union[EndReason, RunInfo]) -> bool:\n        \"\"\"\n        Test if this Pod5 file contains the given object.\n\n        Parameters\n        ----------\n        obj: Union[EndReason, RunInfo]\n            Object to find in this Pod5 file\n\n        Returns\n        -------\n        bool\n            True if obj has already been added to this file\n        \"\"\"\n        return obj in self._index_caches[type(obj)]\n\n    def find(self, obj: Union[EndReason, RunInfo]) -> int:\n        \"\"\"\n        Returns the index of obj in this Pod5 file raising a KeyError if it is missing.\n\n        Parameters\n        ----------\n        obj: Union[EndReason, RunInfo]\n            Obj instance to find in this Pod5 file\n\n        Returns\n        -------\n        int\n            The index of the object in this Pod5 file\n\n        Raises\n        ------\n        KeyError\n            If the object is not in this file\n        \"\"\"\n        try:\n            return self._index_caches[type(obj)][obj]\n        except KeyError as exc:\n            raise KeyError(\n                f\"Could not find index of {obj} in Pod5 file writer: {self}\"\n            ) from exc\n\n    def add_read(self, read: Union[Read, CompressedRead]) -> None:\n        \"\"\"\n        Add a record to the open POD5 file with either compressed or uncompressed\n        signal data depending on the given type of Read.\n\n        Parameters\n        ----------\n        read : Union[Read, CompressedRead]\n            POD5 `Read` or `CompressedRead` object to add as a record to the POD5 file.\n        \"\"\"\n        self.add_reads([read])\n\n    def add_reads(self, reads: Sequence[Union[Read, CompressedRead]]) -> None:\n        \"\"\"\n        Add Read objects (with uncompressed signal data) as records in the open POD5\n        file.\n\n        Parameters\n        ----------\n        reads : Sequence[Union[Read, CompressedRead]\n            Sequence of Read or CompressedRead objects to be added to this POD5 file\n        \"\"\"\n\n        # Nothing to do\n        if not reads:\n            return\n\n        if self._writer is None:\n            raise Pod5ApiException(\"Writer handle has been closed\")\n\n        if isinstance(reads[0], Read):\n            return self._writer.add_reads(  # type: ignore [call-arg]\n                *self._prepare_add_reads_args(reads),\n                [r.signal for r in reads],  # type: ignore\n            )\n        elif isinstance(reads[0], CompressedRead):\n            signal_chunks = [r.signal_chunks for r in reads]  # type: ignore\n            signal_chunk_lengths = [r.signal_chunk_lengths for r in reads]  # type: ignore\n\n            # Array containing the number of chunks for each signal\n            signal_chunk_counts = np.array(\n                [len(samples_per_chunk) for samples_per_chunk in signal_chunk_lengths],\n                dtype=np.uint32,\n            )\n\n            return self._writer.add_reads_pre_compressed(  # type: ignore [call-arg]\n                *self._prepare_add_reads_args(reads),\n                # Join all signal data into one list\n                list(itertools.chain(*signal_chunks)),\n                # Join all read sample counts into one array\n                np.concatenate(signal_chunk_lengths).astype(np.uint32),  # type: ignore [no-untyped-call]\n                signal_chunk_counts,\n            )\n        elif isinstance(reads[0], ReadRecord):\n            raise TypeError(\n                \"Writer.add_reads(reads) does not take ReadRecords - see ReadRecord.to_read()\"\n            )\n        raise TypeError(f\"Writer.add_reads(reads) - unexpected type: {type(reads[0])=}\")\n\n    def _prepare_add_reads_args(self, reads: Sequence[BaseRead]) -> List[Any]:\n        \"\"\"\n        Converts the List of reads into the list of ctypes arrays of data to be supplied\n        to the c api.\n        \"\"\"\n        read_id = np.array(\n            [np.frombuffer(read.read_id.bytes, dtype=np.uint8) for read in reads]\n        )\n        read_number = np.array([read.read_number for read in reads], dtype=np.uint32)\n        start_sample = np.array([read.start_sample for read in reads], dtype=np.uint64)\n        channel = np.array([read.pore.channel for read in reads], dtype=np.uint16)\n        well = np.array([read.pore.well for read in reads], dtype=np.uint8)\n        pore_type = np.array(\n            [self.add(PoreType(read.pore.pore_type)) for read in reads], dtype=np.int16\n        )\n        calib_offset = np.array(\n            [read.calibration.offset for read in reads], dtype=np.float32\n        )\n        calib_scale = np.array(\n            [read.calibration.scale for read in reads], dtype=np.float32\n        )\n        median_before = np.array(\n            [read.median_before for read in reads], dtype=np.float32\n        )\n        end_reason = np.array(\n            [self.add(read.end_reason) for read in reads], dtype=np.int16\n        )\n        end_reason_forced = np.array(\n            [read.end_reason.forced for read in reads], dtype=np.bool_\n        )\n        run_info = np.array([self.add(read.run_info) for read in reads], dtype=np.int16)\n        num_minknow_events = np.array(\n            [read.num_minknow_events for read in reads], dtype=np.uint64\n        )\n        tracked_scaling_scale = np.array(\n            [read.tracked_scaling.scale for read in reads], dtype=np.float32\n        )\n        tracked_scaling_shift = np.array(\n            [read.tracked_scaling.shift for read in reads], dtype=np.float32\n        )\n        predicted_scaling_scale = np.array(\n            [read.predicted_scaling.scale for read in reads], dtype=np.float32\n        )\n        predicted_scaling_shift = np.array(\n            [read.predicted_scaling.shift for read in reads], dtype=np.float32\n        )\n        num_reads_since_mux_change = np.array(\n            [read.num_reads_since_mux_change for read in reads], dtype=np.uint32\n        )\n        time_since_mux_change = np.array(\n            [read.time_since_mux_change for read in reads], dtype=np.float32\n        )\n        open_pore_level = np.array(\n            [read.open_pore_level for read in reads], dtype=np.float32\n        )\n\n        return [\n            read_id.shape[0],\n            read_id,\n            read_number,\n            start_sample,\n            channel,\n            well,\n            pore_type,\n            calib_offset,\n            calib_scale,\n            median_before,\n            end_reason,\n            end_reason_forced,\n            run_info,\n            num_minknow_events,\n            tracked_scaling_scale,\n            tracked_scaling_shift,\n            predicted_scaling_scale,\n            predicted_scaling_shift,\n            num_reads_since_mux_change,\n            time_since_mux_change,\n            open_pore_level,\n        ]\n"
  },
  {
    "path": "python/pod5/src/tests/__init__.py",
    "content": ""
  },
  {
    "path": "python/pod5/src/tests/conftest.py",
    "content": "\"\"\"\nPod5 test fixtures\n\"\"\"\n\nfrom contextlib import contextmanager\nimport os\nfrom datetime import datetime, timezone\nfrom pathlib import Path\nimport psutil\nimport shutil\nimport sys\nfrom typing import Generator, Optional, Set\nfrom uuid import UUID, uuid4, uuid5\n\nimport numpy\nimport numpy.typing\nfrom pod5.pod5_types import ShiftScalePair\nimport pytest\nimport pod5 as p5\n\nTEST_UUID = uuid4()\n\nTEST_DATA_PATH = Path(__file__).parent.parent.parent.parent.parent / \"test_data\"\nPOD5_PATH = TEST_DATA_PATH / \"multi_fast5_zip_v4.pod5\"\n\nPOD5_TEST_SEED = int(os.getenv(\"POD5_TEST_SEED\", numpy.random.randint(1, 9999)))\n\n\nskip_if_windows = pytest.mark.skipif(\n    sys.platform.startswith(\"win\"), reason=\"no symlink privilege on windows CI\"\n)\n\n\n# Run pytest from the tests directory (containing conftest.py) to use this argument\ndef pytest_addoption(parser):\n    \"\"\"Add configurable random seed for testing\"\"\"\n    parser.addoption(\n        \"--pod5-test-seed\",\n        type=int,\n        default=numpy.random.randint(1, 9999),\n        help=\"pod5_factory test seed\",\n    )\n\n\n@contextmanager\ndef assert_no_leaked_handles() -> Generator[None, None, None]:\n    proc = psutil.Process()\n    before = set(proc.open_files())\n    yield\n    after = set(proc.open_files())\n    leaked_handles = after - before\n    leaked_handles = set(h for h in leaked_handles if \".log\" not in str(h.path).lower())\n    if leaked_handles:\n        raise AssertionError(f\"Leaked handles: {leaked_handles}\")\n\n\ndef assert_no_leaked_handles_win(path: Path) -> None:\n    \"\"\"Attempt to rename the file at `path` this shows up leaked handles on windows\"\"\"\n    if sys.platform.lower().startswith(\"win\"):\n        try:\n            os.rename(POD5_PATH, POD5_PATH.with_suffix(\".TEMP\"))\n            os.rename(POD5_PATH.with_suffix(\".TEMP\"), POD5_PATH)\n        except OSError:\n            raise AssertionError(f\"File handle to {path} still open\")\n\n\n@pytest.fixture(scope=\"function\")\ndef reader() -> Generator[p5.Reader, None, None]:\n    \"\"\"Create a Reader from a pod5 file\"\"\"\n    with assert_no_leaked_handles():\n        with p5.Reader(path=POD5_PATH) as reader:\n            yield reader\n    assert_no_leaked_handles_win(POD5_PATH)\n\n\n@pytest.fixture(scope=\"function\")\ndef writer(tmp_path: Path) -> Generator[p5.Writer, None, None]:\n    \"\"\"Create a Pod5Writer to a file in a temporary directory\"\"\"\n    test_pod5 = tmp_path / \"test.pod5\"\n    with p5.Writer(test_pod5) as writer:\n        yield writer\n\n    try:\n        os.rename(test_pod5, test_pod5.with_suffix(\".TEMP\"))\n        os.rename(test_pod5.with_suffix(\".TEMP\"), test_pod5)\n    except OSError:\n        assert False, \"File handle still open\"\n\n\ndef rand_float(seed: int) -> float:\n    \"\"\"Return a random float in the half-open interval [0, 1)\"\"\"\n    numpy.random.seed(seed)\n    return float(numpy.random.rand(1)[0])\n\n\ndef rand_int(low: int, high: int, seed: int) -> int:\n    \"\"\"Returns a random integer in the half-open interval [low, high)\"\"\"\n    numpy.random.seed(seed)\n    return int(numpy.random.randint(low, high))\n\n\ndef rand_str(prefix: str, seed: int) -> str:\n    \"\"\"Create a random string by appending random integer to prefix\"\"\"\n    numpy.random.seed(seed)\n    return f\"{prefix}_{numpy.random.randint(1, 9999999)}\"\n\n\ndef _random_read_id(seed: int = 1) -> UUID:\n    \"\"\"Create a random read_id UUID\"\"\"\n    return uuid5(TEST_UUID, str(seed))\n\n\n@pytest.fixture(scope=\"function\")\ndef random_read_id(request) -> UUID:\n    \"\"\"Create a random read_id UUID\"\"\"\n    return _random_read_id(request.param)\n\n\ndef _random_pore(seed: int) -> p5.Pore:\n    \"\"\"Create a random Pore object\"\"\"\n    return p5.Pore(\n        rand_int(0, 3000, seed), rand_int(0, 4, seed), rand_str(\"pore_type\", seed)\n    )\n\n\n@pytest.fixture(scope=\"function\")\ndef random_pore(request) -> p5.Pore:\n    \"\"\"Create a random Pore object\"\"\"\n    return _random_pore(request.param)\n\n\ndef _random_calibration(seed: int = 1) -> p5.Calibration:\n    \"\"\"Create a random Calibration object\"\"\"\n    return p5.Calibration(rand_float(seed), rand_float(seed + 1))\n\n\n@pytest.fixture(scope=\"function\")\ndef random_calibration(request) -> p5.Calibration:\n    \"\"\"Create a random Calibration object\"\"\"\n    return _random_calibration(request.param)\n\n\ndef _random_end_reason(seed: int = 1) -> p5.EndReason:\n    \"\"\"Create a random EndReason object\"\"\"\n    return p5.EndReason(\n        p5.EndReasonEnum(rand_int(0, 5, seed)), bool(rand_int(0, 1, seed))\n    )\n\n\n@pytest.fixture(scope=\"function\")\ndef random_end_reason(request) -> p5.EndReason:\n    \"\"\"Create a random EndReason object\"\"\"\n    return _random_end_reason(request.param)\n\n\ndef _random_run_info(seed: int = 1) -> p5.RunInfo:\n    \"\"\"Create a random RunInfo object\"\"\"\n    return p5.RunInfo(\n        acquisition_id=rand_str(\"acq_id\", seed),\n        acquisition_start_time=datetime.fromtimestamp(\n            rand_int(0, 1, seed), timezone.utc\n        ),\n        adc_max=rand_int(0, 1000, seed),\n        adc_min=rand_int(-1000, 0, seed),\n        context_tags={rand_str(\"context\", seed): rand_str(\"tag\", seed)},\n        experiment_name=rand_str(\"exp_name\", seed),\n        flow_cell_id=rand_str(\"flow_cell\", seed),\n        flow_cell_product_code=rand_str(\"product_code\", seed),\n        protocol_name=rand_str(\"protocol\", seed),\n        protocol_run_id=rand_str(\"protocol_run_id\", seed),\n        protocol_start_time=datetime.fromtimestamp(rand_int(0, 1, seed), timezone.utc),\n        sample_id=rand_str(\"sample_id\", seed),\n        sample_rate=rand_int(0, 10000, seed),\n        sequencing_kit=rand_str(\"seq_kit\", seed),\n        sequencer_position=rand_str(\"position\", seed),\n        sequencer_position_type=rand_str(\"position_type\", seed),\n        software=rand_str(\"software\", seed),\n        system_name=rand_str(\"system_name\", seed),\n        system_type=rand_str(\"system_type\", seed),\n        tracking_id={rand_str(\"tracking\", seed): rand_str(\"id\", seed)},\n    )\n\n\n@pytest.fixture(scope=\"function\")\ndef random_run_info(request) -> p5.RunInfo:\n    \"\"\"Create a random RunInfo object\"\"\"\n    return _random_run_info(request.param)\n\n\ndef _random_signal(seed: int = 1) -> numpy.typing.NDArray[numpy.int16]:\n    \"\"\"Generate a random signal\"\"\"\n    numpy.random.seed(seed)\n    size = rand_int(0, 200_000, seed)\n    return numpy.random.randint(-32768, 32767, size, dtype=numpy.int16)\n\n\n@pytest.fixture(scope=\"function\")\ndef random_signal(request) -> numpy.typing.NDArray[numpy.int16]:\n    \"\"\"Generate a random signal\"\"\"\n    return _random_signal(request.param)\n\n\ndef _random_read(seed: int = 1) -> p5.Read:\n    \"\"\"Generate a Read with random data\"\"\"\n    signal = _random_signal(seed)\n    return p5.Read(\n        read_id=_random_read_id(seed),\n        pore=_random_pore(seed),\n        calibration=_random_calibration(seed),\n        read_number=rand_int(0, 100000, seed),\n        start_sample=rand_int(0, 10000000, seed),\n        median_before=rand_float(seed),\n        end_reason=_random_end_reason(seed),\n        run_info=_random_run_info(seed % 4),\n        predicted_scaling=ShiftScalePair(rand_float(seed), rand_float(seed + 1)),\n        tracked_scaling=ShiftScalePair(rand_float(seed + 2), rand_float(seed + 3)),\n        signal=signal,\n    )\n\n\n@pytest.fixture(scope=\"function\")\ndef random_read(request) -> p5.Read:\n    \"\"\"Generate a Read with random data\"\"\"\n    return _random_read(request.param)\n\n\ndef _random_read_pre_compressed(seed: int = 1) -> p5.CompressedRead:\n    \"\"\"Generate a Read with random data\"\"\"\n    signal = _random_signal(seed)\n    return p5.CompressedRead(\n        read_id=_random_read_id(seed),\n        pore=_random_pore(seed),\n        calibration=_random_calibration(seed),\n        read_number=rand_int(0, 100000, seed),\n        start_sample=rand_int(0, 10000000, seed),\n        median_before=rand_float(seed),\n        end_reason=_random_end_reason(seed),\n        run_info=_random_run_info(seed % 4),\n        predicted_scaling=ShiftScalePair(rand_float(seed), rand_float(seed + 1)),\n        tracked_scaling=ShiftScalePair(rand_float(seed + 2), rand_float(seed + 3)),\n        signal_chunks=[p5.vbz_compress_signal(signal)],\n        signal_chunk_lengths=[len(signal)],\n    )\n\n\n@pytest.fixture(scope=\"function\")\ndef random_read_pre_compressed(request) -> p5.CompressedRead:\n    \"\"\"Generate a Read with random data\"\"\"\n    return _random_read_pre_compressed(request.param)\n\n\ndef _seeder(seed: int) -> Generator[int, None, None]:\n    \"\"\"Generates seed values for numpy.rand.seed\"\"\"\n    idx = 0\n    while True:\n        value = (seed + idx) % 2**23\n        idx += 13\n        yield value\n\n\n@pytest.fixture(scope=\"session\")\ndef pod5_factory(request, tmp_path_factory: pytest.TempPathFactory, pytestconfig):\n    \"\"\"\n    Create and cache a temporary pod5 file of `n_records` random reads with a\n    default name unless given `name_parts` like `subdir/my.pod5`. Files\n    are cached under their path and are cleaned.\n    \"\"\"\n\n    POD5_TEST_SEED = pytestconfig.getoption(\"pod5_test_seed\")\n\n    tmp_path = tmp_path_factory.mktemp(\"pod5_factory\")\n    existing_pod5s: Set[Path] = set([])\n\n    seeder = _seeder(POD5_TEST_SEED)\n\n    def _pod5_factory(\n        n_records: int = 100,\n        name: Optional[str] = None,\n    ) -> Path:\n        \"\"\"Generate pod5 files with `n_records` with an optionally specified `name`\"\"\"\n        assert n_records > 0\n\n        if name:\n            path = tmp_path / name\n        else:\n            path = tmp_path / f\"pod5_fixture_{n_records}.pod5\"\n\n        if path in existing_pod5s:\n            if path.is_file():\n                return path\n            existing_pod5s.remove(path)\n\n        reads = [_random_read(seed=next(seeder)) for _ in range(n_records)]\n        with p5.Writer(path=path, software_name=\"pod5_pytest_fixture\") as writer:\n            writer.add_reads(reads)\n\n        existing_pod5s.add(path)\n        assert path.is_file()\n        return path\n\n    yield _pod5_factory\n\n    for path in existing_pod5s:\n        path.unlink()\n\n    # Write the test seed to stdout, need to disable capturemanager first\n    capmanager = request.config.pluginmanager.getplugin(\"capturemanager\")\n    with capmanager.global_and_fixture_disabled():\n        print(f\"\\n\\nPOD5_TEST_SEED: {POD5_TEST_SEED}\")\n\n\n@pytest.fixture(scope=\"session\")\ndef nested_dataset(tmp_path_factory: pytest.TempPathFactory, pod5_factory) -> Path:\n    \"\"\"\n    Creates a nested directory structure with temporary pod5 files.\n\n    Symbolic links are only created when not running on windows systems.\n\n    ./root/root_10.pod5\n    ./root/subdir/subdir_11.pod5\n    ./root/subdir/symbolic_9.pod5 --> ../../outer/symbolic_9.pod5\n    ./root/subdir/subsubdir/subsubdir_12.pod5\n    ./root/subdir/subsubdir/empty.txt\n    ./root/linked/ --> ../linked/\n\n    ./outer/symbolic_9.pod5\n    ./linked/linked_8.pod5\n\n    Returns path to root/\n    \"\"\"\n    tmp_path = tmp_path_factory.mktemp(\"pod5_nested_directory\")\n    root = tmp_path / \"root\"\n    sub_dir = root / \"subdir\"\n    subsub_dir = sub_dir / \"subsubdir\"\n    Path.mkdir(subsub_dir, parents=True)\n\n    root_pod5: Path = pod5_factory(10)\n    subdir_pod5: Path = pod5_factory(11)\n    subsubdir_pod5: Path = pod5_factory(12)\n\n    shutil.copyfile(str(root_pod5), str(root / \"root_10.pod5\"))\n    shutil.copyfile(str(subdir_pod5), str(sub_dir / \"subdir_11.pod5\"))\n    shutil.copyfile(str(subsubdir_pod5), str(subsub_dir / \"subsubdir_12.pod5\"))\n\n    (subsub_dir / \"empty.txt\").touch()\n\n    # Linked file\n    outer_dir = tmp_path / \"outer\"\n    Path.mkdir(outer_dir, parents=True)\n    symbolic_pod5: Path = pod5_factory(9)\n    symb_path = outer_dir / \"symbolic_9.pod5\"\n\n    if not sys.platform.startswith(\"win\"):\n        shutil.copyfile(str(symbolic_pod5), str(symb_path))\n        (sub_dir / \"symbolic_9.pod5\").symlink_to(symb_path)\n    else:\n        shutil.copyfile(str(symbolic_pod5), str((sub_dir / \"symbolic_9.pod5\")))\n\n    # Linked directory\n    linked_src_dir = tmp_path / \"linked\"\n    Path.mkdir(linked_src_dir, parents=True)\n    linked_pod5: Path = pod5_factory(8)\n    shutil.copyfile(str(linked_pod5), str(linked_src_dir / \"linked_8.pod5\"))\n\n    if not sys.platform.startswith(\"win\"):\n        (root / \"linked\").symlink_to(linked_src_dir)\n    else:\n        (root / \"linked\").mkdir(parents=True)\n        shutil.copyfile(str(linked_pod5), str(root / \"linked\" / \"linked_8.pod5\"))\n\n    return root\n"
  },
  {
    "path": "python/pod5/src/tests/test_api.py",
    "content": "import tempfile\nfrom datetime import datetime, timezone\nfrom pathlib import Path\nfrom typing import Union\nfrom uuid import UUID, uuid4, uuid5\n\nimport numpy as np\nimport pytest\n\nimport pod5 as p5\nfrom pod5.api_utils import format_read_ids, pack_read_ids\nfrom pod5.writer import Writer\n\nTEST_UUID = uuid4()\n\n\ndef gen_test_read(seed, compressed=False) -> Union[p5.Read, p5.CompressedRead]:\n    np.random.seed(seed)\n\n    def get_random_float() -> float:\n        return float(np.random.rand(100000)[0])\n\n    def get_random_int(low: int, high: int) -> int:\n        return int(np.random.randint(low, high, 1)[0])\n\n    def get_random_str(prefix: str) -> str:\n        return f\"{prefix}_{np.random.randint(100000)}\"\n\n    size = get_random_int(0, 1000)\n    signal = np.random.randint(0, 1024, size, dtype=np.int16)\n\n    cls = p5.Read  # type: ignore\n    signal_args = {\"signal\": signal}  # type: ignore\n\n    if compressed:\n        cls = p5.CompressedRead  # type: ignore\n        signal_args = {\n            \"signal_chunks\": [p5.signal_tools.vbz_compress_signal(signal)],  # type: ignore\n            \"signal_chunk_lengths\": [len(signal)],  # type: ignore\n        }\n\n    return cls(\n        uuid5(TEST_UUID, str(seed)),\n        p5.Pore(\n            get_random_int(0, 3000),\n            get_random_int(0, 4),\n            get_random_str(\"pore_type\"),\n        ),\n        p5.Calibration(get_random_float(), get_random_float()),\n        get_random_int(0, 100000),\n        get_random_int(0, 10000000),\n        get_random_float(),\n        p5.EndReason(\n            p5.EndReasonEnum(get_random_int(0, 5)), bool(get_random_int(0, 1))\n        ),\n        p5.RunInfo(\n            get_random_str(\"acq_id\"),\n            datetime.fromtimestamp(get_random_int(0, 1), timezone.utc),\n            get_random_int(0, 1000),\n            get_random_int(-1000, 0),\n            {get_random_str(\"context\"): get_random_str(\"tag\")},\n            get_random_str(\"exp_name\"),\n            get_random_str(\"flow_cell\"),\n            get_random_str(\"product_code\"),\n            get_random_str(\"protocol\"),\n            get_random_str(\"protocol_run_id\"),\n            datetime.fromtimestamp(get_random_int(0, 1), timezone.utc),\n            get_random_str(\"sample_id\"),\n            get_random_int(0, 10000),\n            get_random_str(\"seq_kit\"),\n            get_random_str(\"position\"),\n            get_random_str(\"position_type\"),\n            get_random_str(\"software\"),\n            get_random_str(\"system_name\"),\n            get_random_str(\"system_type\"),\n            {get_random_str(\"tracking\"): get_random_str(\"id\")},\n        ),\n        num_minknow_events=5,\n        tracked_scaling=p5.pod5_types.ShiftScalePair(10.0, 50),\n        predicted_scaling=p5.pod5_types.ShiftScalePair(5.0, 100.0),\n        num_reads_since_mux_change=123,\n        time_since_mux_change=456.0,\n        open_pore_level=1234.0,\n        **signal_args,\n    )\n\n\ndef run_writer_test(f: Writer):\n    writer_supports_compressed = f.signal_compression_type == p5.SignalType.VbzSignal\n\n    test_read = gen_test_read(0, compressed=False)\n    print(\"read\", test_read.read_id, test_read.run_info.adc_max)\n    f.add_read(test_read)\n\n    test_read = gen_test_read(1, compressed=writer_supports_compressed)\n    print(\"read\", test_read.read_id, test_read.run_info.adc_max)\n    f.add_read(test_read)\n\n    test_reads = [\n        gen_test_read(2),\n        gen_test_read(3),\n        gen_test_read(4),\n        gen_test_read(5),\n    ]\n    print(\"read\", test_reads[0].read_id, test_reads[0].run_info.adc_max)\n    f.add_reads(test_reads)\n\n    test_reads = [\n        gen_test_read(6, compressed=writer_supports_compressed),\n        gen_test_read(7, compressed=writer_supports_compressed),\n        gen_test_read(8, compressed=writer_supports_compressed),\n        gen_test_read(9, compressed=writer_supports_compressed),\n    ]\n    f.add_reads(test_reads)\n    assert test_reads[0].sample_count > 0\n\n\ndef run_reader_test(reader: p5.Reader):\n    # Check top level file metadata\n\n    assert reader.writing_software == \"Python API\"\n    assert reader.file_identifier != UUID(int=0)\n\n    read_count = 0\n    read_id_strs = set()\n    for idx, read in enumerate(reader.reads()):\n        read_count += 1\n        data = gen_test_read(idx)\n\n        read_id_strs.add(str(read.read_id))\n\n        assert isinstance(data, p5.Read)\n\n        assert data.read_id == read.read_id\n        assert data.read_number == read.read_number\n        assert data.start_sample == read.start_sample\n        assert pytest.approx(data.median_before) == read.median_before\n\n        assert data.pore == read.pore\n        assert pytest.approx(data.calibration.offset) == read.calibration.offset\n        assert pytest.approx(data.calibration.scale) == read.calibration.scale\n        assert data.run_info == read.run_info\n        assert (\n            data.run_info.adc_max - data.run_info.adc_min + 1\n            == read.calibration_digitisation\n        )\n        assert (\n            pytest.approx(\n                data.calibration.scale\n                * (data.run_info.adc_max - data.run_info.adc_min + 1)\n            )\n            == read.calibration_range\n        )\n        assert data.end_reason.name == read.end_reason.name\n        assert data.end_reason.forced == read.end_reason.forced\n\n        assert data.num_minknow_events == read.num_minknow_events\n        assert data.tracked_scaling == read.tracked_scaling\n        assert data.predicted_scaling == read.predicted_scaling\n        assert data.num_reads_since_mux_change == read.num_reads_since_mux_change\n        assert data.time_since_mux_change == read.time_since_mux_change\n        assert data.open_pore_level == read.open_pore_level\n\n        assert data.sample_count == read.sample_count\n        # Expecting poor compression given the random input\n        assert 0 < read.byte_count < (len(data.signal) * data.signal.itemsize + 24)\n        assert len(read.signal_rows) >= 1\n\n        assert not read.has_cached_signal\n        assert (read.signal == data.signal).all()\n        assert (\n            pytest.approx(read.signal_pa)\n            == (data.signal + data.calibration.offset) * data.calibration.scale\n        )\n        chunk_signals = [read.signal_for_chunk(i) for i in range(len(read.signal_rows))]\n        assert (np.concatenate(chunk_signals) == data.signal).all()\n        assert isinstance(read.end_reason_index, int)\n        assert read.end_reason_index == read.end_reason.reason.value\n        assert isinstance(read.run_info_index, int)\n\n    assert reader.num_reads == read_count\n    assert set(reader.read_ids) == read_id_strs\n\n    # Try to walk through the file in read batches:\n    for idx, batch in enumerate(reader.read_batches(preload={\"samples\"})):\n        assert len(batch.cached_samples_column) == batch.num_reads\n\n    # Try to walk through specific batches in the file:\n    for batch in reader.read_batches(batch_selection=[0], preload={\"samples\"}):\n        assert len(batch.cached_samples_column) == batch.num_reads\n        assert len(batch.cached_sample_count_column) == batch.num_reads\n        for idx, read in enumerate(batch.reads()):\n            data = gen_test_read(idx)\n            assert isinstance(data, p5.Read)\n            assert read.has_cached_signal\n            assert (read.signal == data.signal).all()\n\n    # Try to walk through all reads in the file:\n    for idx, read in enumerate(reader.reads(preload={\"samples\"})):\n        data = gen_test_read(idx)\n\n        assert isinstance(data, p5.Read)\n        assert read.has_cached_signal\n        assert (read.signal == data.signal).all()\n\n    # Try to walk through some reads in the file with a bad read id, not ignoring bad ids\n    with pytest.raises(RuntimeError):\n        for idx, read in enumerate(reader.reads([\"bad-id\"], missing_ok=False)):\n            # Shouldn't hit this!\n            assert False\n\n    # Try to walk through some reads in the file with a bad read id, ignoring bad ids\n    for idx, read in enumerate(reader.reads([\"bad-id\"], missing_ok=True)):\n        # Shouldn't hit this!\n        assert False\n\n    reads = list(reader.reads())\n    search_reads = [\n        reads[6],\n        reads[3],\n        reads[1],\n    ]\n\n    search = reader.reads([str(r.read_id) for r in search_reads])\n\n    found_ids = set()\n    for i, searched_read in enumerate(search):\n        found_ids.add(searched_read.read_id)\n    assert found_ids == set(r.read_id for r in search_reads)\n\n\n@pytest.mark.filterwarnings(\"ignore: pod5.\")\ndef test_pyarrow_from_pathlib():\n    with tempfile.TemporaryDirectory() as temp:\n        path = Path(temp) / \"example.pod5\"\n        with p5.Writer(path) as _fh:\n            run_writer_test(_fh)\n\n        with p5.Reader(path) as _fh:\n            run_reader_test(_fh)\n\n\n@pytest.mark.filterwarnings(\"ignore: pod5.\")\ndef test_pyarrow_from_str():\n    with tempfile.TemporaryDirectory() as temp:\n        path = str(Path(temp) / \"example.pod5\")\n        with p5.Writer(path) as _fh:\n            run_writer_test(_fh)\n\n        with p5.Reader(path) as _fh:\n            run_reader_test(_fh)\n\n\n@pytest.mark.filterwarnings(\"ignore: pod5.\")\ndef test_pyarrow_from_pathlib_uncompressed():\n    with tempfile.TemporaryDirectory() as temp:\n        path = Path(temp) / \"example.pod5\"\n        with p5.Writer(\n            path, signal_compression_type=p5.SignalType.UncompressedSignal\n        ) as _fh:\n            run_writer_test(_fh)\n\n        with p5.Reader(path) as _fh:\n            run_reader_test(_fh)\n\n\ndef test_read_id_packing():\n    \"\"\"\n    Assert pack_read_ids repacks and format_read_ids correctly unpacks collections\n    of read ids\n    \"\"\"\n    rids = [str(uuid4()) for _ in range(10)]\n    packed_rids = pack_read_ids(rids)\n\n    assert len(rids) == 10\n    assert isinstance(packed_rids, np.ndarray)\n    assert packed_rids.dtype == np.uint8\n\n    unpacked_rids = format_read_ids(packed_rids)\n    assert isinstance(unpacked_rids, list)\n\n    for rid, unpacked in zip(rids, unpacked_rids):\n        assert type(rid) is type(unpacked)\n        assert rid == unpacked\n"
  },
  {
    "path": "python/pod5/src/tests/test_convert_from_fast5.py",
    "content": "\"\"\"\nTest for the convert_from_fast5 tool\n\"\"\"\n\nimport datetime\nimport multiprocessing as mp\nfrom pathlib import Path\nimport queue\nimport sys\nfrom typing import Dict\nfrom unittest.mock import MagicMock, Mock, patch\nfrom uuid import UUID\n\nimport h5py\nimport numpy as np\n\nimport pytest\n\nimport pod5\nfrom pod5.tools.pod5_convert_from_fast5 import (\n    OutputHandler,\n    QueueManager,\n    convert_datetime_as_epoch_ms,\n    convert_fast5_end_reason,\n    convert_fast5_files,\n    convert_fast5_read,\n    convert_from_fast5,\n    convert_run_info,\n    get_read_from_fast5,\n    handle_exception,\n    is_multi_read_fast5,\n    logger,\n)\n\n\nTEST_DATA_PATH = Path(__file__).parent.parent.parent.parent.parent / \"test_data\"\nFAST5_PATH = TEST_DATA_PATH / \"multi_fast5_zip.fast5\"\n\nSINGLE_READ_FAST5_PATH = (\n    TEST_DATA_PATH\n    / \"single_read_fast5/fe85b517-62ee-4a33-8767-41cab5d5ab39.fast5.single-read\"\n)\n\nEXPECTED_POD5_RESULTS = {\n    \"0000173c-bf67-44e7-9a9c-1ad0bc728e74\": pod5.Read(\n        read_id=UUID(\"0000173c-bf67-44e7-9a9c-1ad0bc728e74\"),\n        pore=pod5.Pore(\n            channel=109,\n            well=4,\n            pore_type=\"not_set\",\n        ),\n        calibration=pod5.Calibration.from_range(\n            offset=21.0,\n            adc_range=1437.6976318359375,\n            digitisation=8192.0,\n        ),\n        read_number=1093,\n        start_sample=4534321,\n        median_before=183.1077423095703,\n        end_reason=pod5.EndReason(\n            pod5.EndReasonEnum.UNKNOWN,\n            False,\n        ),\n        run_info=pod5.RunInfo(\n            acquisition_id=\"a08e850aaa44c8b56765eee10b386fc3e516a62b\",\n            acquisition_start_time=datetime.datetime(\n                2019, 5, 13, 11, 11, 43, tzinfo=datetime.timezone.utc\n            ),\n            adc_max=4095,\n            adc_min=-4096,\n            context_tags={\n                \"basecall_config_filename\": \"dna_r9.4.1_450bps_fast.cfg\",\n                \"experiment_duration_set\": \"180\",\n                \"experiment_type\": \"genomic_dna\",\n                \"package\": \"bream4\",\n                \"package_version\": \"4.0.6\",\n                \"sample_frequency\": \"4000\",\n                \"sequencing_kit\": \"sqk-lsk108\",\n            },\n            experiment_name=\"\",\n            flow_cell_id=\"\",\n            flow_cell_product_code=\"\",\n            protocol_name=\"c449127e3461a521e0865fe6a88716f6f6b0b30c\",\n            protocol_run_id=\"df049455-3552-438c-8176-d4a5b1dd9fc5\",\n            protocol_start_time=datetime.datetime(\n                1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc\n            ),\n            sample_id=\"TEST_SAMPLE\",\n            sample_rate=4000,\n            sequencing_kit=\"sqk-lsk108\",\n            sequencer_position=\"MS00000\",\n            sequencer_position_type=\"minion\",\n            software=\"python-pod5-converter\",\n            system_name=\"\",\n            system_type=\"\",\n            tracking_id={\n                \"asic_id\": \"131070\",\n                \"asic_id_eeprom\": \"0\",\n                \"asic_temp\": \"35.043102\",\n                \"asic_version\": \"IA02C\",\n                \"auto_update\": \"0\",\n                \"auto_update_source\": \"https://mirror.oxfordnanoportal.com/software/MinKNOW/\",\n                \"bream_is_standard\": \"0\",\n                \"device_id\": \"MS00000\",\n                \"device_type\": \"minion\",\n                \"distribution_status\": \"modified\",\n                \"distribution_version\": \"unknown\",\n                \"exp_script_name\": \"c449127e3461a521e0865fe6a88716f6f6b0b30c\",\n                \"exp_script_purpose\": \"sequencing_run\",\n                \"exp_start_time\": \"2019-05-13T11:11:43Z\",\n                \"flow_cell_id\": \"\",\n                \"guppy_version\": \"3.0.3+7e7b7d0\",\n                \"heatsink_temp\": \"35.000000\",\n                \"hostname\": \"happy_fish\",\n                \"installation_type\": \"prod\",\n                \"local_firmware_file\": \"1\",\n                \"operating_system\": \"ubuntu 16.04\",\n                \"protocol_group_id\": \"TEST_EXPERIMENT\",\n                \"protocol_run_id\": \"df049455-3552-438c-8176-d4a5b1dd9fc5\",\n                \"protocols_version\": \"4.0.6\",\n                \"run_id\": \"a08e850aaa44c8b56765eee10b386fc3e516a62b\",\n                \"sample_id\": \"TEST_SAMPLE\",\n                \"usb_config\": \"MinION_fx3_1.1.1_ONT#MinION_fpga_1.1.0#ctrl#Auto\",\n                \"version\": \"3.4.0-rc3\",\n            },\n        ),\n        # Values are not checked but the length here is from manual inspection\n        signal=np.array([1] * 123627, dtype=np.int16),\n    ),\n    \"008468c3-e477-46c4-a6e2-7d021a4ebf0b\": pod5.Read(\n        read_id=UUID(\"008468c3-e477-46c4-a6e2-7d021a4ebf0b\"),\n        pore=pod5.Pore(channel=2, well=2, pore_type=\"not_set\"),\n        calibration=pod5.Calibration.from_range(\n            offset=4.0,\n            adc_range=1437.6976318359375,\n            digitisation=8192.0,\n        ),\n        read_number=411,\n        start_sample=2510647,\n        median_before=219.04641723632812,\n        end_reason=pod5.EndReason(reason=pod5.EndReasonEnum.UNKNOWN, forced=False),\n        run_info=pod5.RunInfo(\n            acquisition_id=\"a08e850aaa44c8b56765eee10b386fc3e516a62b\",\n            acquisition_start_time=datetime.datetime(\n                2019, 5, 13, 11, 11, 43, tzinfo=datetime.timezone.utc\n            ),\n            adc_max=4095,\n            adc_min=-4096,\n            context_tags={\n                \"basecall_config_filename\": \"dna_r9.4.1_450bps_fast.cfg\",\n                \"experiment_duration_set\": \"180\",\n                \"experiment_type\": \"genomic_dna\",\n                \"package\": \"bream4\",\n                \"package_version\": \"4.0.6\",\n                \"sample_frequency\": \"4000\",\n                \"sequencing_kit\": \"sqk-lsk108\",\n            },\n            experiment_name=\"\",\n            flow_cell_id=\"\",\n            flow_cell_product_code=\"\",\n            protocol_name=\"c449127e3461a521e0865fe6a88716f6f6b0b30c\",\n            protocol_run_id=\"df049455-3552-438c-8176-d4a5b1dd9fc5\",\n            protocol_start_time=datetime.datetime(\n                1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc\n            ),\n            sample_id=\"TEST_SAMPLE\",\n            sample_rate=4000,\n            sequencing_kit=\"sqk-lsk108\",\n            sequencer_position=\"MS00000\",\n            sequencer_position_type=\"minion\",\n            software=\"python-pod5-converter\",\n            system_name=\"\",\n            system_type=\"\",\n            tracking_id={\n                \"asic_id\": \"131070\",\n                \"asic_id_eeprom\": \"0\",\n                \"asic_temp\": \"35.043102\",\n                \"asic_version\": \"IA02C\",\n                \"auto_update\": \"0\",\n                \"auto_update_source\": \"https://mirror.oxfordnanoportal.com/software/MinKNOW/\",\n                \"bream_is_standard\": \"0\",\n                \"device_id\": \"MS00000\",\n                \"device_type\": \"minion\",\n                \"distribution_status\": \"modified\",\n                \"distribution_version\": \"unknown\",\n                \"exp_script_name\": \"c449127e3461a521e0865fe6a88716f6f6b0b30c\",\n                \"exp_script_purpose\": \"sequencing_run\",\n                \"exp_start_time\": \"2019-05-13T11:11:43Z\",\n                \"flow_cell_id\": \"\",\n                \"guppy_version\": \"3.0.3+7e7b7d0\",\n                \"heatsink_temp\": \"35.000000\",\n                \"hostname\": \"happy_fish\",\n                \"installation_type\": \"prod\",\n                \"local_firmware_file\": \"1\",\n                \"operating_system\": \"ubuntu 16.04\",\n                \"protocol_group_id\": \"TEST_EXPERIMENT\",\n                \"protocol_run_id\": \"df049455-3552-438c-8176-d4a5b1dd9fc5\",\n                \"protocols_version\": \"4.0.6\",\n                \"run_id\": \"a08e850aaa44c8b56765eee10b386fc3e516a62b\",\n                \"sample_id\": \"TEST_SAMPLE\",\n                \"usb_config\": \"MinION_fx3_1.1.1_ONT#MinION_fpga_1.1.0#ctrl#Auto\",\n                \"version\": \"3.4.0-rc3\",\n            },\n        ),\n        # Values are not checked but the length here is from manual inspection\n        signal=np.array([1] * 206976, dtype=np.int16),\n    ),\n}\n\n\ndef unraisablehook(unraisable):\n    print(unraisable.exc_type, unraisable.exc_value, unraisable.exc_traceback)\n\n\nsys.unraisablehook = unraisablehook\n\n\nclass TestFast5Conversion:\n    \"\"\"Test the fast5 to pod5 conversion\"\"\"\n\n    def test_convert_fast5_read(self) -> None:\n        \"\"\"\n        Test known good fast5 reads\n        \"\"\"\n        run_info_cache: Dict[str, pod5.RunInfo] = {}\n\n        with h5py.File(str(FAST5_PATH), \"r\") as _f5:\n            for read_id, expected_read in EXPECTED_POD5_RESULTS.items():\n                read = convert_fast5_read(\n                    _f5[f\"read_{read_id}\"],\n                    run_info_cache,\n                )\n\n                assert expected_read.end_reason == read.end_reason\n                assert expected_read.calibration == read.calibration\n                assert expected_read.pore == read.pore\n                assert expected_read.run_info == read.run_info\n                assert expected_read.read_number == read.read_number\n                assert expected_read.start_sample == read.start_sample\n                assert expected_read.median_before == read.median_before\n\n                signal = read.decompressed_signal\n                assert expected_read.signal.shape[0] == signal.shape[0]\n                assert signal.dtype == np.int16\n\n    @pytest.mark.parametrize(\n        \"fast5,expected\",\n        [\n            (0, pod5.EndReasonEnum.UNKNOWN),\n            (1, pod5.EndReasonEnum.UNKNOWN),\n            (2, pod5.EndReasonEnum.MUX_CHANGE),\n            (3, pod5.EndReasonEnum.UNBLOCK_MUX_CHANGE),\n            (4, pod5.EndReasonEnum.DATA_SERVICE_UNBLOCK_MUX_CHANGE),\n            (5, pod5.EndReasonEnum.SIGNAL_POSITIVE),\n            (6, pod5.EndReasonEnum.SIGNAL_NEGATIVE),\n        ],\n    )\n    def test_end_reason(self, fast5: int, expected: pod5.EndReasonEnum) -> None:\n        exp = pod5.EndReason.from_reason_with_default_forced(expected)\n        assert exp == convert_fast5_end_reason(fast5)\n\n    def test_convert_run_info_defaults(self) -> None:\n        result = convert_run_info(\n            acq_id=\"acq_id\",\n            adc_max=1,\n            adc_min=1,\n            sample_rate=1,\n            context_tags={},\n            device_type=\"dev_type\",\n            tracking_id={},\n        )\n\n        epoch = convert_datetime_as_epoch_ms(f\"{datetime.datetime.utcfromtimestamp(0)}\")\n        assert isinstance(result, pod5.RunInfo)\n        assert result.acquisition_id == \"acq_id\"\n        assert result.acquisition_start_time == epoch\n        assert result.adc_max == 1\n        assert result.adc_min == 1\n        assert result.context_tags == {}\n        assert result.experiment_name == \"\"\n        assert result.flow_cell_id == \"\"\n        assert result.flow_cell_product_code == \"\"\n        assert result.protocol_name == \"\"\n        assert result.protocol_run_id == \"\"\n        assert result.protocol_start_time == epoch\n        assert result.sample_id == \"\"\n        assert result.sample_rate == 1\n        assert result.sequencing_kit == \"\"\n        assert result.sequencer_position == \"\"\n        assert result.sequencer_position_type == \"dev_type\"\n        assert result.software == \"python-pod5-converter\"\n        assert result.system_name == \"\"\n        assert result.system_type == \"\"\n        assert result.tracking_id == {}\n\n    def test_convert_run_info(self) -> None:\n        result = convert_run_info(\n            acq_id=\"_acq_id\",\n            adc_max=2,\n            adc_min=3,\n            sample_rate=4,\n            context_tags={\"sequencing_kit\": b\"sequencing_kit\", \"ctag\": b\"ctag\"},\n            device_type=\"_dev_type\",\n            tracking_id={\n                \"exp_start_time\": f\"{datetime.datetime.utcfromtimestamp(1)}\",\n                \"flow_cell_id\": b\"flow_cell_id\",\n                \"flow_cell_product_code\": b\"flow_cell_product_code\",\n                \"exp_script_name\": b\"exp_script_name\",\n                \"protocol_run_id\": b\"protocol_run_id\",\n                \"protocol_start_time\": f\"{datetime.datetime.utcfromtimestamp(2)}\",\n                \"sample_id\": b\"sample_id\",\n                \"sequencing_kit\": b\"sequencing_kit\",\n                \"device_id\": b\"device_id\",\n                \"device_type\": b\"device_type\",\n                \"host_product_serial_number\": b\"host_product_serial_number\",\n                \"host_product_code\": b\"host_product_code\",\n            },\n        )\n\n        assert isinstance(result, pod5.RunInfo)\n        assert result.acquisition_id == \"_acq_id\"\n        assert result.acquisition_start_time == convert_datetime_as_epoch_ms(\n            f\"{datetime.datetime.utcfromtimestamp(1)}\"\n        )\n        assert result.adc_max == 2\n        assert result.adc_min == 3\n        assert result.context_tags == {\n            \"sequencing_kit\": \"sequencing_kit\",\n            \"ctag\": \"ctag\",\n        }\n        assert result.experiment_name == \"\"\n        assert result.flow_cell_id == \"flow_cell_id\"\n        assert result.flow_cell_product_code == \"flow_cell_product_code\"\n        assert result.protocol_name == \"exp_script_name\"\n        assert result.protocol_run_id == \"protocol_run_id\"\n        assert result.protocol_start_time == convert_datetime_as_epoch_ms(\n            f\"{datetime.datetime.utcfromtimestamp(2)}\"\n        )\n        assert result.sample_id == \"sample_id\"\n        assert result.sample_rate == 4\n        assert result.sequencing_kit == \"sequencing_kit\"\n        assert result.sequencer_position == \"device_id\"\n        assert result.sequencer_position_type == \"device_type\"\n        assert result.software == \"python-pod5-converter\"\n        assert result.system_name == \"host_product_serial_number\"\n        assert result.system_type == \"host_product_code\"\n\n\nclass TestFast5Detection:\n    def test_single_read_fast5_detection(self):\n        \"\"\"Test single-read fast5 files are detected raising an assertion error\"\"\"\n        assert not is_multi_read_fast5(SINGLE_READ_FAST5_PATH)\n\n    def test_multi_read_fast5_detection(self):\n        \"\"\"Test multi-read fast5 files are detected not raising an error\"\"\"\n        assert is_multi_read_fast5(FAST5_PATH)\n\n    def test_read_id_keys_detected(self) -> None:\n        \"\"\"Test that only read_id groups are returned from a known good file\"\"\"\n        with h5py.File(str(FAST5_PATH), \"r\") as _f5:\n            for group_key in _f5.keys():\n                assert get_read_from_fast5(group_key, _f5) is not None\n\n    def test_unknown_keys_ignored(self) -> None:\n        \"\"\"Test that non-read_id keys are ignored\"\"\"\n        with h5py.File(str(FAST5_PATH), \"r\") as _f5:\n            assert get_read_from_fast5(\"bad_key\", _f5) is None\n\n    def test_bad_keys_skipped_with_warning(self) -> None:\n        \"\"\"Test that read_id keys which are bad are skipped raising a warning\"\"\"\n        with h5py.File(str(FAST5_PATH), \"r\") as _f5:\n            with pytest.warns(UserWarning, match=\"Failed to read key\"):\n                # Good reads should start with read_ prefix. This will cause a key error\n                assert get_read_from_fast5(\"read_bad_key\", _f5) is None\n\n\nclass TestConvertBehaviour:\n    \"\"\"Test the runtime behaviour of the conversion tool based on the cli arguments\"\"\"\n\n    def test_no_unforced_overwrite(self, tmp_path: Path):\n        \"\"\"Assert that the conversion tool will not overwrite existing files\"\"\"\n\n        existing = tmp_path / \"exists.pod5\"\n        existing.touch()\n        with pytest.raises(FileExistsError):\n            convert_from_fast5(inputs=[FAST5_PATH], output=existing)\n\n    def test_forced_overwrite(self, tmp_path: Path):\n        \"\"\"Assert that the conversion tool will overwrite existing file if forced\"\"\"\n\n        existing = tmp_path / \"exists.pod5\"\n        existing.touch()\n        convert_from_fast5(inputs=[FAST5_PATH], output=existing, force_overwrite=True)\n\n    def test_directory_output(self, tmp_path: Path):\n        \"\"\"\n        Assert that the conversion tool will write to a output directory creating\n        a default named output.pod5 file\n        \"\"\"\n\n        assert len(list(tmp_path.rglob(\"*\"))) == 0\n        convert_from_fast5(inputs=[FAST5_PATH], output=tmp_path)\n        assert len(list(tmp_path.rglob(\"*.pod5\"))) == 1\n        assert (tmp_path / \"output.pod5\").exists()\n\n    def test_single_file_output(self, tmp_path: Path):\n        \"\"\"Assert that the conversion tool will write to a specified file path\"\"\"\n\n        output = tmp_path / \"filename.pod5\"\n        assert len(list(tmp_path.rglob(\"*\"))) == 0\n        convert_from_fast5(inputs=[FAST5_PATH], output=output)\n        assert len(list(tmp_path.rglob(\"*\"))) == 1\n        assert output.exists()\n\n    def test_output_121_relative(self, tmp_path: Path):\n        \"\"\"\n        Assert that the conversion tool will not write one-to-one files as expected\n        \"\"\"\n\n        clone_1 = tmp_path / \"clone1.fast5\"\n        clone_1.write_bytes(FAST5_PATH.read_bytes())\n\n        clone_2 = tmp_path / \"subdir/clone2.fast5\"\n        clone_2.parent.mkdir(parents=True, exist_ok=False)\n        clone_2.write_bytes(FAST5_PATH.read_bytes())\n\n        output = tmp_path / \"output\"\n        output.mkdir(parents=True, exist_ok=True)\n\n        convert_from_fast5(\n            inputs=[clone_1, clone_2],\n            output=output,\n            one_to_one=tmp_path,\n            strict=True,\n        )\n\n        assert (output / \"clone1.pod5\").exists()\n        assert (output / \"subdir/clone2.pod5\").exists()\n\n    def test_output_121_relative_no_parents(self, tmp_path: Path):\n        \"\"\"\n        Assert that the conversion tool will not write one-to-one files outside of the\n        desired output folder\n        \"\"\"\n\n        clone_1 = tmp_path / \"relative_parent.fast5\"\n        clone_1.write_bytes(FAST5_PATH.read_bytes())\n\n        clone_2 = tmp_path / \"subdir/clone2.fast5\"\n        clone_2.parent.mkdir(parents=True, exist_ok=False)\n        clone_2.write_bytes(FAST5_PATH.read_bytes())\n\n        output = tmp_path / \"output\"\n        output.mkdir(parents=True, exist_ok=True)\n\n        with pytest.raises(RuntimeError, match=\"directory must be a relative parent\"):\n            convert_from_fast5(\n                inputs=[clone_1, clone_2],\n                output=output,\n                one_to_one=tmp_path / \"subdir\",\n                strict=True,\n            )\n\n\nclass TestOutputHandler:\n    def test_output_handler_default_writer(self, tmp_path: Path):\n        \"\"\"Assert that the OutputHandler creates an output file with default name\"\"\"\n        handler = OutputHandler(tmp_path, None, False)\n        source = tmp_path / \"test.fast5\"\n        writer = handler.get_writer(source)\n\n        assert isinstance(writer, pod5.Writer)\n        assert writer.path == tmp_path / \"output.pod5\"\n\n        handler.close_all()\n        assert writer._writer is None\n        assert len(list(tmp_path.glob(\"*.pod5\"))) == 1\n\n    def test_output_handler_one_to_one_writer(self, tmp_path: Path):\n        \"\"\"Assert that the OutputHandler creates output name is similar when in 1:1\"\"\"\n        handler = OutputHandler(tmp_path, tmp_path, False)\n        source = tmp_path / \"test.fast5\"\n        writer = handler.get_writer(source)\n\n        assert isinstance(writer, pod5.Writer)\n        assert writer.path == tmp_path / \"test.pod5\"\n\n        handler.close_all()\n        assert writer._writer is None\n        assert len(list(tmp_path.glob(\"*.pod5\"))) == 1\n\n    def test_output_handler_one_to_one_multiple_writer(self, tmp_path: Path):\n        \"\"\"Assert that the OutputHandler creates output name is similar when in 1:1\"\"\"\n        handler = OutputHandler(tmp_path, tmp_path, False)\n\n        names = [\"test1.fast5\", \"test2.fast5\", \"example.fast5\"]\n        for name in names:\n            writer = handler.get_writer(tmp_path / name)\n\n            assert isinstance(writer, pod5.Writer)\n            assert writer.path == (tmp_path / name).with_suffix(\".pod5\")\n\n        assert len(list(tmp_path.glob(\"*.pod5\"))) == len(names)\n        handler.close_all()\n\n    def test_no_reopen(self, tmp_path: Path):\n        \"\"\"Assert that the OutputHandler will not overwrite files\"\"\"\n        handler = OutputHandler(tmp_path, tmp_path, False)\n        example = tmp_path / \"example\"\n        handler.get_writer(example)\n        handler.set_input_complete(example, is_exception=False)\n        with pytest.raises(FileExistsError, match=\"Trying to re-open\"):\n            handler.get_writer(example)\n\n    def test_none_if_exception(self, tmp_path: Path):\n        \"\"\"Assert that the OutputHandler will not overwrite files\"\"\"\n        handler = OutputHandler(tmp_path, tmp_path, False)\n        example = tmp_path / \"example\"\n        handler.get_writer(example)\n        handler.set_input_complete(example, is_exception=True)\n        assert handler.get_writer(example) is None\n\n    def test_no_duplicate_open(self, tmp_path: Path):\n        \"\"\"Assert that the OutputHandler will reuse handles\"\"\"\n        handler = OutputHandler(tmp_path, tmp_path, False)\n        example = tmp_path / \"example\"\n        writer1 = handler.get_writer(example)\n        writer2 = handler.get_writer(example)\n        assert writer1 == writer2\n\n\nclass TestQueueManager:\n    def test_shutdown(self, monkeypatch, caplog: pytest.LogCaptureFixture):\n        logger.disabled = False\n        monkeypatch.setenv(\"POD5_DEBUG\", \"1\")\n        threads, timeout = 5, 0.05\n        ctx = mp.get_context(\"spawn\")\n        queues = QueueManager(ctx, [FAST5_PATH], threads, timeout)\n        n_inputs, n_req, n_data, n_exc = queues.shutdown()\n        assert n_inputs == 1\n        assert \"Unfinished inputs\" in caplog.messages[0]\n        assert n_req == threads * 2\n        assert n_data == 0\n        assert n_exc == 0\n\n        for getter in [\n            queues.await_data,\n            queues.await_request,\n            queues.get_exception,\n            queues.get_input,\n        ]:\n            with pytest.raises((OSError, ValueError), match=\"is closed\"):\n                # OSError changed to ValueError in py3.8\n                getter()\n\n        queues.shutdown()\n\n    def test_shutdown_with_work(self, monkeypatch, caplog: pytest.LogCaptureFixture):\n        logger.disabled = False\n        monkeypatch.setenv(\"POD5_DEBUG\", \"1\")\n        threads, timeout = 1, 0.05\n        ctx = mp.get_context(\"spawn\")\n        queues = QueueManager(ctx, [FAST5_PATH], threads, timeout)\n        queues.enqueue_data(None, None)\n        queues.enqueue_exception(Path.cwd(), Exception(\"blah\"), \"text\")\n        n_inputs, n_req, n_data, n_exc = queues.shutdown()\n        assert n_inputs == 1\n        assert \"Unfinished inputs\" in caplog.messages[0]\n        assert n_req == threads * 2\n        assert n_data == 1\n        assert \"Unfinished data\" in caplog.messages[1]\n        assert n_exc == 1\n        assert \"Unfinished exceptions\" in caplog.messages[2]\n\n        for getter in [\n            queues.await_data,\n            queues.await_request,\n            queues.get_exception,\n            queues.get_input,\n        ]:\n            with pytest.raises((OSError, ValueError), match=\"is closed\"):\n                # OSError changed to ValueError in py3.8\n                getter()\n\n        queues.shutdown()\n\n    def test_blocked_by_requests(self) -> None:\n        threads, timeout = 3, 0.05\n        ctx = mp.get_context(\"spawn\")\n        queues = QueueManager(ctx, [FAST5_PATH], threads, timeout)\n        # Exhaust the requests queue\n        for _ in range(queues._requests_size):\n            queues.await_request()\n\n        with pytest.raises(TimeoutError, match=\"No progress\"):\n            queues.await_request()\n\n        queues.enqueue_request()\n        queues.await_request()\n\n        with pytest.raises(TimeoutError, match=\"No progress\"):\n            queues.await_data()\n\n        queues.shutdown()\n\n    def test_data_queue(self) -> None:\n        threads, timeout = 5, 0.05\n        ctx = mp.get_context(\"spawn\")\n        queues = QueueManager(ctx, [FAST5_PATH], threads, timeout)\n\n        # Assert initially empty\n        with pytest.raises(TimeoutError, match=\"No progress\"):\n            queues.await_data()\n\n        queues.enqueue_data(FAST5_PATH, [])\n        queues.enqueue_data(FAST5_PATH, 100)\n        queues.enqueue_data(None, None)\n\n        queues.await_request()\n        assert queues.await_data() == (FAST5_PATH, [])\n        assert queues.await_data() == (FAST5_PATH, 100)\n        assert queues.await_data() == (None, None)\n\n        # Assert await data for list of reads replaced the request by checking\n        # for requests being Full\n        queues.enqueue_data(FAST5_PATH, [])\n        with pytest.raises(queue.Full):\n            queues.await_data()\n\n        # Assert only 1 request taken and replaced\n        n_inputs, n_req, n_data, n_exc = queues.shutdown()\n        assert n_req == threads * 2\n\n    def test_exception_queue(self) -> None:\n        threads, timeout = 5, 0.05\n        ctx = mp.get_context(\"spawn\")\n        queues = QueueManager(ctx, [FAST5_PATH], threads, timeout)\n\n        # Assert initially empty\n        assert queues.get_exception() is None\n\n        queues.enqueue_exception(FAST5_PATH, Exception(\"foo\"), \"bar\")\n        item = queues.get_exception()\n        assert item is not None\n        path, exc, trace = item\n        assert path == FAST5_PATH\n        with pytest.raises(Exception, match=\"foo\"):\n            raise exc\n        assert trace == \"bar\"\n\n        # Assert only 1 request taken and replaced\n        n_inputs, n_req, n_data, n_exc = queues.shutdown()\n        assert n_req == threads * 2\n\n\nclass TestConvertLoop:\n    def test_convert_fast5_files_file_type_exceptions(self, tmp_path: Path) -> None:\n        nf5 = tmp_path / \"not_a.fast5\"\n        nf5.touch()\n        threads, timeout = 5, 0.05\n        ctx = mp.get_context(\"spawn\")\n        queues = QueueManager(ctx, [nf5], threads, timeout)\n        convert_fast5_files(queues)\n\n        exception = queues.get_exception()\n        assert exception is not None\n        path, exc, _ = exception\n        assert path == nf5\n\n        queues.shutdown()\n        with pytest.raises(TypeError, match=\"not a multi-read fast5\"):\n            raise exc\n\n    def test_convert_fast5_files_breaks_loop(self) -> None:\n        threads, timeout = 1, 0.05\n        ctx = mp.get_context(\"spawn\")\n        qm = QueueManager(ctx, [], threads, timeout)\n        convert_fast5_files(qm)\n\n        exception = qm.get_exception()\n        assert exception is None\n\n        # Assert sentinel enqueued\n        path, data = qm.await_data()\n        assert path is None\n        assert data is None\n\n        qm.shutdown()\n\n    @patch(\"pod5.tools.pod5_convert_from_fast5.convert_fast5_file\")\n    def test_convert_fast5_file_exception(self, mock: Mock) -> None:\n        threads, timeout = 5, 0.05\n        ctx = mp.get_context(\"spawn\")\n        qm = QueueManager(ctx, [FAST5_PATH], threads, timeout)\n        mock.side_effect = Exception\n        convert_fast5_files(qm)\n        exception = qm.get_exception()\n        assert exception is not None\n        path, exc, _ = exception\n        assert path == FAST5_PATH\n        with pytest.raises(Exception):\n            raise exc\n\n        path, data = qm.await_data()\n        assert path is None\n        assert data is None\n\n        qm.shutdown()\n\n    def test_handle_exception(self) -> None:\n        hndlr = MagicMock()\n        status = MagicMock()\n        exc = (FAST5_PATH, Exception(\"foo\"), \"bar\")\n        with pytest.raises(Exception, match=\"foo\"):\n            handle_exception(exc, hndlr, status, True)\n\n        hndlr.set_input_complete.assert_called_once_with(FAST5_PATH, is_exception=True)\n        status.write.assert_called_once_with(\"foo\", sys.stderr)\n        status.close.assert_called_once()\n\n        assert handle_exception(exc, hndlr, status, False) is None\n"
  },
  {
    "path": "python/pod5/src/tests/test_convert_to_fast5.py",
    "content": "\"\"\"\nTest for the convert_from_fast5 tool\n\"\"\"\n\nfrom pathlib import Path\n\nimport numpy as np\nimport pytest\n\nimport pod5 as p5\nfrom pod5.tools.pod5_convert_from_fast5 import convert_from_fast5\nfrom pod5.tools.pod5_convert_to_fast5 import convert_to_fast5\n\nTEST_DATA_PATH = Path(__file__).parent.parent.parent.parent.parent / \"test_data\"\nFAST5_PATH = TEST_DATA_PATH / \"multi_fast5_zip.fast5\"\nPOD5_PATH = TEST_DATA_PATH / \"multi_fast5_zip_v4.pod5\"\n\n\nclass TestPod5ConversionRoundTrip:\n    \"\"\"Test the pod5 to fast5 conversion and back again to assert consistency\"\"\"\n\n    def test_convert_pod5_to_fast5_and_back(self, tmp_path: Path) -> None:\n        \"\"\"\n        Test known good pod5 file converts to a fast5 file and back again\n        \"\"\"\n\n        # Convert to fast5\n        convert_to_fast5([POD5_PATH], tmp_path, False)\n\n        fast5_paths = list(tmp_path.glob(\"*.fast5\"))\n        assert len(fast5_paths) == 1\n\n        # Expected filename has input filename with some extra indexing data\n        expected_fast5_name = \"multi_fast5_zip_v4.0_0.fast5\"\n        assert fast5_paths[0].name == expected_fast5_name\n\n        # Convert back to pod5\n        convert_from_fast5(fast5_paths, tmp_path)\n\n        pod5_paths = list(tmp_path.glob(\"*.pod5\"))\n        assert len(pod5_paths) == 1\n\n        # Ensure we aren't cheating\n        assert POD5_PATH != pod5_paths[0]\n\n        expected_tested_reads, count_tested_reads = (10, 0)\n        with p5.Reader(POD5_PATH) as original:\n            with p5.Reader(pod5_paths[0]) as converted:\n                for original_record, converted_record in zip(original, converted):\n                    # Assert fields are identical\n                    assert original_record.read_id == converted_record.read_id\n                    assert original_record.read_number == converted_record.read_number\n                    assert (\n                        original_record.num_minknow_events\n                        == converted_record.num_minknow_events\n                    )\n                    assert original_record.calibration == converted_record.calibration\n                    assert original_record.end_reason == converted_record.end_reason\n                    assert original_record.pore == converted_record.pore\n                    assert original_record.run_info == converted_record.run_info\n                    assert original_record.read_number == converted_record.read_number\n                    assert original_record.start_sample == converted_record.start_sample\n                    assert original_record.num_samples == converted_record.num_samples\n                    assert np.array_equal(\n                        original_record.signal, converted_record.signal\n                    )\n\n                    count_tested_reads += 1\n\n        # Assert we didn't miss any reads\n        assert count_tested_reads == expected_tested_reads\n\n\nclass TestConvertBehaviour:\n    \"\"\"Test the runtime behaviour of the conversion tool based on the cli arguments\"\"\"\n\n    def test_no_unforced_overwrite(self, tmp_path: Path):\n        \"\"\"Assert that the conversion tool will not overwrite existing files\"\"\"\n\n        existing = tmp_path / \"multi_fast5_zip_v4.0_0.fast5\"\n        existing.touch()\n        with pytest.raises(FileExistsError):\n            convert_to_fast5(inputs=[POD5_PATH], output=tmp_path, force_overwrite=False)\n\n    def test_forced_overwrite(self, tmp_path: Path):\n        \"\"\"Assert that the conversion tool will overwrite existing file if forced\"\"\"\n\n        existing = tmp_path / \"multi_fast5_zip_v4.0_0.fast5\"\n        existing.touch()\n        created_time = existing.stat().st_mtime_ns\n        convert_to_fast5(inputs=[POD5_PATH], output=tmp_path, force_overwrite=True)\n\n        # Assert the file has been replaces\n        assert existing.stat().st_mtime_ns > created_time\n\n    def test_recursive_input(self, tmp_path: Path):\n        \"\"\"Assert that the conversion finds pod5s in subdirs\"\"\"\n\n        subdir = tmp_path / \"sub/subsub/\"\n        subdir.mkdir(parents=True)\n        src = subdir / \"input.pod5\"\n        src.write_bytes(POD5_PATH.read_bytes())\n\n        convert_to_fast5(inputs=[tmp_path], output=tmp_path, recursive=True)\n        assert len(list(tmp_path.glob(\"*.fast5\")))\n\n    def test_multiple_outputs(self, tmp_path: Path):\n        \"\"\"\n        Assert that the conversion tool will write multiple files where the\n        files-read-count is low\n        \"\"\"\n        expect = [\n            tmp_path / \"multi_fast5_zip_v4.0_0.fast5\",\n            tmp_path / \"multi_fast5_zip_v4.1_0.fast5\",\n        ]\n\n        assert len(list(tmp_path.rglob(\"*\"))) == 0\n\n        # input reads == 10 so expect 2 files\n        convert_to_fast5(inputs=[POD5_PATH], output=tmp_path, file_read_count=5)\n        fast5s_found = list(tmp_path.rglob(\"*.fast5\"))\n        assert len(fast5s_found) == 2\n\n        for fast5 in fast5s_found:\n            assert fast5 in expect\n"
  },
  {
    "path": "python/pod5/src/tests/test_dataset.py",
    "content": "import random\nimport shutil\nimport warnings\nfrom pathlib import Path\nfrom uuid import uuid4\n\nimport numpy as np\nimport pytest\n\nimport pod5 as p5\nfrom pod5.api_utils import Pod5ApiException\nfrom pod5.reader import ReadRecord\nfrom tests.conftest import (\n    POD5_PATH,\n    assert_no_leaked_handles,\n    assert_no_leaked_handles_win,\n)\n\nPOD5_PATH_EXPECTED_NUM_READS = 10\n\n# NO LINKS ON WINDOWS\n# nested_dataset/\n# ./root/root_10.pod5\n# ./root/subdir/subdir_11.pod5\n# ./root/subdir/symbolic_9.pod5 --> ../../outer/symbolic_9.pod5\n# ./root/subdir/subsubdir/subsubdir_12.pod5\n# ./root/subdir/subsubdir/empty.txt\n# ./root/linked/ --> ../linked/\n\n# ./outer/symbolic_9.pod5\n# ./linked/linked_8.pod5\n\nEXPECT_FILE_COUNT_RECURSIVE = 5\nEXPECT_READ_COUNT_RECURSIVE = 8 + 9 + 10 + 11 + 12\nEXPECT_FILE_COUNT_ROOT = 1\nEXPECT_READ_COUNT_ROOT = 10\n\n\nclass TestDatasetReader:\n    \"\"\"\n    Test the DatasetReader\n    \"\"\"\n\n    def test_bad_file_num_reads(self, tmp_path: Path) -> None:\n        empty = tmp_path / \"empty.pod5\"\n        empty.touch()\n\n        with p5.DatasetReader(empty) as dataset:\n            with pytest.raises(Pod5ApiException, match=\"DatasetReader error reading:\"):\n                dataset.num_reads\n\n    def test_len_single(self) -> None:\n        assert (\n            p5.Reader(POD5_PATH).num_reads\n            == len(p5.DatasetReader(POD5_PATH))\n            == POD5_PATH_EXPECTED_NUM_READS\n        )\n\n    def test_iter_single(self) -> None:\n        observed = set()\n        for record in p5.DatasetReader(POD5_PATH):\n            assert isinstance(record, ReadRecord)\n            observed.add(record.read_id)\n        assert len(observed) == POD5_PATH_EXPECTED_NUM_READS\n\n    def test_no_recursive(self, nested_dataset: Path) -> None:\n        \"\"\"Test root directory file discovery only\"\"\"\n        with p5.DatasetReader(nested_dataset) as dataset:\n            expected_path = nested_dataset / \"root_10.pod5\"\n            assert dataset._paths == [expected_path]\n            assert len(dataset) == 10\n\n            reader = dataset.get_reader(expected_path)\n            observed_count = 0\n            for reader_read, dataset_read in zip(reader, dataset):\n                observed_count += 1\n                assert reader_read.read_id == dataset_read.read_id\n            assert observed_count == len(dataset)\n\n    def test_recursive(self, nested_dataset: Path) -> None:\n        \"\"\"Test recursive file discovery\"\"\"\n        dataset = p5.DatasetReader(nested_dataset, recursive=True)\n\n        assert len(dataset.paths) == EXPECT_FILE_COUNT_RECURSIVE\n        assert len(dataset) == EXPECT_READ_COUNT_RECURSIVE\n\n        assert dataset._index is None\n\n        # Extremely unlikely that there will be a  collision in 40 UUIDs\n        assert not dataset.has_duplicate()\n        assert dataset._index is not None\n        assert dataset.num_reads == len(dataset._index)\n\n        observed_count = 0\n        for path in dataset.paths:\n            reader = dataset.get_reader(path)\n\n            for read_id in reader.read_ids:\n                observed_count += 1\n                read_record = dataset.get_read(read_id=read_id)\n                assert read_record is not None\n                read_record.read_id == read_id\n                isinstance(read_record, ReadRecord)\n\n        assert observed_count == len(dataset)\n\n    def test_get_reader_is_cached(self, nested_dataset: Path) -> None:\n        \"\"\"Tests that a reader is cached\"\"\"\n        dataset = p5.DatasetReader(nested_dataset)\n        root_path = nested_dataset / \"root_10.pod5\"\n        reader = dataset.get_reader(root_path)\n        reader_clone = dataset.get_reader(root_path)\n        assert id(reader) == id(reader_clone)\n        assert isinstance(reader, p5.Reader)\n        assert reader.path == root_path\n\n        cache_info = dataset._get_reader.cache_info()  # type: ignore[attr-defined]\n        assert cache_info.hits == 1\n\n    def test_reader_all_cache(self, nested_dataset: Path) -> None:\n        \"\"\"Tests that readers are cached and do not leak handles on close\"\"\"\n        REPEATS = 5000\n        with assert_no_leaked_handles():\n            ds = p5.DatasetReader(\n                nested_dataset, recursive=True, max_cached_readers=None\n            )\n            paths = ds.paths\n            list(ds.get_reader(p) for p in paths * REPEATS)\n            cache_info = ds._get_reader.cache_info()  # type: ignore[attr-defined]\n            assert cache_info.maxsize is None\n            assert cache_info.currsize == len(ds.paths)\n            assert cache_info.misses == len(ds.paths)\n            assert cache_info.hits == (REPEATS - 1) * len(ds.paths)\n            del ds\n\n        for p in paths:\n            assert_no_leaked_handles_win(p)\n\n    def test_reader_all_cache_context(self, nested_dataset: Path) -> None:\n        \"\"\"Tests that readers are cached and do not leak handles on close\"\"\"\n        REPEATS = 5000\n        with assert_no_leaked_handles():\n            with p5.DatasetReader(\n                nested_dataset, recursive=True, max_cached_readers=None\n            ) as ds:\n                paths = ds.paths\n                list(ds.get_reader(p) for p in paths * REPEATS)\n                cache_info = ds._get_reader.cache_info()  # type: ignore[attr-defined]\n                assert cache_info.maxsize is None\n                assert cache_info.currsize == len(ds.paths)\n                assert cache_info.misses == len(ds.paths)\n                assert cache_info.hits == (REPEATS - 1) * len(ds.paths)\n\n        for p in paths:\n            assert_no_leaked_handles_win(p)\n\n    def test_reader_no_cache(self, nested_dataset: Path) -> None:\n        \"\"\"Tests that no reader cache is used if set\"\"\"\n        REPEATS = 500\n        with assert_no_leaked_handles():\n            ds = p5.DatasetReader(nested_dataset, recursive=True, max_cached_readers=0)\n            paths = ds.paths\n            for p in paths * REPEATS:\n                ds.get_reader(p)\n            cache_info = ds._get_reader.cache_info()  # type: ignore[attr-defined]\n            assert cache_info.maxsize == 0\n            assert cache_info.currsize == 0\n            assert cache_info.misses == REPEATS * len(ds.paths)\n            assert cache_info.hits == 0\n\n            # No call to del here tests that the no handles are kept open in the ds\n\n        for p in paths:\n            assert_no_leaked_handles_win(p)\n\n    def test_reader_clear_readers(self, nested_dataset: Path) -> None:\n        \"\"\"Tests that the cache is reader cache cleared without leaking handles\"\"\"\n        REPEATS = 500\n        with assert_no_leaked_handles():\n            ds = p5.DatasetReader(\n                nested_dataset, recursive=True, max_cached_readers=None\n            )\n            list(ds.get_reader(p) for p in ds.paths * REPEATS)\n            cache_info_before = ds._get_reader.cache_info()  # type: ignore[attr-defined]\n            assert cache_info_before.currsize > 0\n            ds.clear_readers()\n            cache_info_after = ds._get_reader.cache_info()  # type: ignore[attr-defined]\n            assert cache_info_after.currsize == 0\n\n            # No call to del here tests that the no handles are kept open in the ds\n\n        for p in ds.paths:\n            assert_no_leaked_handles_win(p)\n\n    def test_reader_cache_delete(self, nested_dataset: Path) -> None:\n        with assert_no_leaked_handles():\n            with p5.DatasetReader(\n                nested_dataset, recursive=True, max_cached_readers=1\n            ) as ds:\n                list(ds.get_reader(p) for p in ds.paths)\n                cache_info_before = ds._get_reader.cache_info()  # type: ignore[attr-defined]\n                assert cache_info_before.currsize == 1\n                reader = ds.get_reader(ds.paths[0])\n                reader_id = id(reader)\n                del reader\n                assert reader_id == id(ds.get_reader(ds.paths[0]))\n\n                paths = ds.paths\n\n        for p in paths:\n            assert_no_leaked_handles_win(p)\n\n    def test_random_read_indexing(self, nested_dataset: Path) -> None:\n        \"\"\"Test randomly selecting by read id\"\"\"\n        dataset = p5.DatasetReader(nested_dataset, recursive=True)\n\n        observed_count = 0\n        for read_id in sorted(dataset.read_ids):\n            observed_count += 1\n            read_record = dataset.get_read(read_id)\n            assert read_record is not None\n            assert str(read_record.read_id) == read_id\n\n        assert observed_count == len(dataset) == EXPECT_READ_COUNT_RECURSIVE\n\n        assert dataset.get_read(\"\") is None\n        assert dataset.get_read(\"foo\") is None\n\n    def test_duplicate_read_selection(self, nested_dataset: Path) -> None:\n        \"\"\"Test explicitly selecting a read multiple times\"\"\"\n        dataset = p5.DatasetReader(nested_dataset, recursive=True)\n        all_read_ids = list(dataset.read_ids)\n        assert len(all_read_ids) > 0\n        read_id = random.choice(all_read_ids)\n\n        selection = [read_id, read_id, read_id]\n\n        loaded_reads = list(dataset.reads(selection=selection))\n        assert len(loaded_reads) == len(selection)\n\n        for read in loaded_reads:\n            assert str(read.read_id) == read_id\n            assert np.array_equal(read.signal, loaded_reads[0].signal)\n\n    def test_missing_id_does_not_return_member(self, nested_dataset: Path) -> None:\n        \"\"\"\n        Test that a valid read_id (uuid4) which is not in the dataset doesn't return\n        a previously loaded member of the dataset\n        \"\"\"\n        dataset = p5.DatasetReader(nested_dataset, recursive=True)\n        all_read_ids = list(dataset.read_ids)\n        assert len(all_read_ids) > 0\n\n        for _ in range(1_000):\n            member_ids = set(random.sample(all_read_ids, len(all_read_ids) // 5))\n            assert len(member_ids) > 0\n\n            # Create a read id which is not in the dataset\n            missing_id = uuid4()\n            assert missing_id not in member_ids, \"statistically impossible\"\n            # Add the missing (but valid) read id to the selection\n            selection = list(member_ids)\n            selection.append(str(missing_id))\n\n            # Assert only the member read_ids are loaded\n            loaded_reads = list(dataset.reads(selection=selection))\n            loaded_ids = set(str(read.read_id) for read in loaded_reads)\n            assert missing_id not in loaded_ids\n            assert loaded_ids == member_ids\n\n    def test_invalid_id_does_not_return_member(self, nested_dataset: Path) -> None:\n        \"\"\"\n        Test that invalid selections after valid ones do not return a previous\n        valid read\n        \"\"\"\n        dataset = p5.DatasetReader(nested_dataset, recursive=True)\n        all_read_ids = list(dataset.read_ids)\n        assert len(all_read_ids) > 0\n\n        for _ in range(100):\n            valid_ids = set(random.sample(all_read_ids, len(all_read_ids) // 5))\n\n            selection = list(valid_ids)\n            selection.append(\"\")\n\n            loaded_reads = list(dataset.reads(selection=selection))\n            loaded_ids = list(str(read.read_id) for read in loaded_reads)\n            assert len(loaded_ids) == len(valid_ids)\n            assert set(loaded_ids) == valid_ids\n\n    def test_empty_read_selection(self, nested_dataset: Path) -> None:\n        \"\"\"Test empty selection returns no reads\"\"\"\n        dataset = p5.DatasetReader(nested_dataset, recursive=True)\n        loaded_reads = list(dataset.reads(selection=[]))\n        assert len(loaded_reads) == 0\n\n    def test_prompt_read_indexing(self, nested_dataset: Path) -> None:\n        \"\"\"Test prompt indexing\"\"\"\n        with p5.DatasetReader(nested_dataset, recursive=True, index=True) as ds:\n            assert ds._index is not None\n            assert len(ds._index) == EXPECT_READ_COUNT_RECURSIVE\n            assert set(ds._index.keys()) == set(ds.read_ids)\n\n    def test_iter_multi(self, nested_dataset: Path) -> None:\n        \"\"\"Test __iter__ yields all records\"\"\"\n        dataset = p5.DatasetReader(nested_dataset, recursive=True)\n\n        observed_count = 0\n        for read_record in dataset:\n            observed_count += 1\n\n            assert read_record is not None\n            assert str(read_record.read_id)\n\n        assert observed_count == len(dataset) == EXPECT_READ_COUNT_RECURSIVE\n\n    def test_iter_multi_single_thread(self, nested_dataset: Path) -> None:\n        \"\"\"\n        Test __iter__ yields selected records while threads is one (no multi-threading)\n        \"\"\"\n        dataset = p5.DatasetReader(nested_dataset, recursive=True, threads=1)\n\n        expected_count = int(len(dataset) // 1.4)\n        sample = random.sample(list(dataset.read_ids), expected_count)\n\n        observed_count = 0\n        observed_read_ids = set()\n        for read_record in dataset.reads(selection=sample):\n            observed_count += 1\n            observed_read_ids.add(str(read_record.read_id))\n\n        assert observed_count == expected_count == len(observed_read_ids)\n        assert observed_read_ids == set(sample)\n\n    def test_iter_multi_multi_thread(self, nested_dataset: Path) -> None:\n        \"\"\"Test __iter__ yields selected records using multi-threading\"\"\"\n        dataset = p5.DatasetReader(nested_dataset, recursive=True, threads=4)\n\n        expected_count = int(len(dataset) // 1.3)\n        sample = random.sample(list(dataset.read_ids), expected_count)\n\n        observed_count = 0\n        observed_read_ids = set()\n        for read_record in dataset.reads(selection=sample):\n            observed_count += 1\n            observed_read_ids.add(str(read_record.read_id))\n\n        assert observed_count == expected_count == len(observed_read_ids)\n        assert observed_read_ids == set(sample)\n\n    def test_iter_multi_multi_thread_no_cache(self, nested_dataset: Path) -> None:\n        \"\"\"\n        Test __iter__ yields selected records from multiple files while\n        multi-threading without caching readers\n        \"\"\"\n        dataset = p5.DatasetReader(\n            nested_dataset, recursive=True, threads=4, max_cached_readers=0\n        )\n\n        expected_count = int(len(dataset) // 1.5)\n        sample = random.sample(list(dataset.read_ids), expected_count)\n\n        observed_count = 0\n        observed_read_ids = set()\n        for read_record in dataset.reads(selection=sample):\n            observed_count += 1\n            observed_read_ids.add(str(read_record.read_id))\n\n        assert observed_count == expected_count == len(observed_read_ids)\n        assert observed_read_ids == set(sample)\n\n    def test_mixed_load(self, nested_dataset: Path) -> None:\n        \"\"\"Test passing file and directory paths\"\"\"\n        dataset = p5.DatasetReader(\n            [nested_dataset, nested_dataset, POD5_PATH, POD5_PATH], recursive=True\n        )\n        assert len(dataset) == EXPECT_READ_COUNT_RECURSIVE + 10\n        assert len(dataset.paths) == EXPECT_FILE_COUNT_RECURSIVE + 1\n\n    def test_iter_selection(self, nested_dataset: Path) -> None:\n        \"\"\"Test read_id selection\"\"\"\n        dataset = p5.DatasetReader([nested_dataset], recursive=True)\n\n        sample_size = 20\n        read_ids = set(random.sample(list(dataset.read_ids), sample_size))\n        observed_count = 0\n        for read_record in dataset.reads(selection=read_ids):\n            observed_count += 1\n            assert str(read_record.read_id) in read_ids\n\n        assert observed_count == sample_size\n\n    def test_iter_duplicate(self, nested_dataset: Path) -> None:\n        \"\"\"Selecting duplicate read_ids yields multiple copies\"\"\"\n        dataset = p5.DatasetReader([nested_dataset], recursive=True)\n\n        sample_size = 5\n        multiplier = 2\n        duplicated_read_ids = (\n            random.sample(list(dataset.read_ids), sample_size) * multiplier\n        )\n        observed_count = 0\n        for read_record in dataset.reads(selection=duplicated_read_ids):\n            observed_count += 1\n            assert str(read_record.read_id) in set(duplicated_read_ids)\n\n        assert observed_count == sample_size * multiplier\n\n    def test_duplicate(self, tmp_path: Path) -> None:\n        \"\"\"Selecting from duplicate files yields each copy\"\"\"\n        pod5_copy = tmp_path / \"copy.pod5\"\n        shutil.copyfile(POD5_PATH, pod5_copy)\n        dataset = p5.DatasetReader([POD5_PATH, pod5_copy])\n\n        assert dataset.has_duplicate()\n\n        unique_ids = set(dataset.read_ids)\n        observed_count = 0\n        for read_record in dataset.reads(selection=unique_ids):\n            observed_count += 1\n            assert str(read_record.read_id) in unique_ids\n        assert observed_count == len(unique_ids) * 2\n\n    def test_duplicate_index_warns(self, tmp_path: Path) -> None:\n        \"\"\"Indexing from duplicate files warns of consequences and can be suppressed\"\"\"\n        pod5_copy = tmp_path / \"copy.pod5\"\n        shutil.copyfile(POD5_PATH, pod5_copy)\n        dataset = p5.DatasetReader([POD5_PATH, pod5_copy])\n\n        assert dataset.has_duplicate()\n        with pytest.warns(Warning, match=\"read_ids found in dataset\"):\n            dataset.get_path(next(dataset.read_ids))\n\n        with pytest.warns(Warning, match=\"read_ids found in dataset\"):\n            dataset.get_read(next(dataset.read_ids))\n\n        dataset.warn_duplicate_indexing = False\n        with warnings.catch_warnings():\n            warnings.simplefilter(\"error\")\n            dataset.get_path(next(dataset.read_ids))\n            dataset.get_read(next(dataset.read_ids))\n\n    def test_iter_missing(self, nested_dataset: Path) -> None:\n        \"\"\"Missing read_ids are not found\"\"\"\n        dataset = p5.DatasetReader([nested_dataset], recursive=True)\n\n        sample_size = 5\n        read_ids = random.sample(list(dataset.read_ids), sample_size)\n        missing_ids = [str(uuid4()) for _ in range(50)]\n\n        observed_count = 0\n        for read_record in dataset.reads(selection=read_ids + missing_ids):\n            observed_count += 1\n            assert str(read_record.read_id) in set(read_ids)\n\n        assert observed_count == sample_size\n\n    def test_get_path(self, nested_dataset: Path) -> None:\n        \"\"\"Get the path to underlying file from read_id\"\"\"\n        dataset = p5.DatasetReader([nested_dataset], recursive=True)\n\n        assert dataset.get_read(str(uuid4())) is None\n\n        for path in dataset.paths:\n            read_id = dataset.get_reader(path).read_ids[0]\n            assert dataset.get_path(read_id) == path\n\n    def test_collect_paths(self, nested_dataset: Path) -> None:\n        \"\"\"Pass various inputs to DatasetReader._collect_dataset\"\"\"\n        collect = p5.DatasetReader._collect_dataset\n\n        expected = {nested_dataset / \"root_10.pod5\"}\n        kwargs = dict(recursive=False, pattern=\"*.pod5\", threads=1)\n        assert expected == collect(nested_dataset, **kwargs)  # type: ignore\n        assert expected == collect(str(nested_dataset), **kwargs)  # type: ignore\n\n        with pytest.raises(TypeError, match=\"paths must be a Collection\"):\n            collect(1, **kwargs)  # type: ignore\n"
  },
  {
    "path": "python/pod5/src/tests/test_filter.py",
    "content": "from pathlib import Path\nfrom random import sample\nfrom typing import List\nfrom uuid import UUID\nfrom pod5.tools.pod5_view import view_pod5\n\nimport polars as pl\n\nimport pod5 as p5\nfrom pod5.tools.pod5_filter import parse_read_id_targets, filter_pod5\nfrom pod5.tools.pod5_subset import PL_DEST_FNAME, PL_READ_ID\n\nfrom tests.conftest import skip_if_windows\nimport pytest\n\n\nTEST_DATA_PATH = Path(__file__).parent.parent.parent.parent.parent / \"test_data\"\nPOD5_PATH = TEST_DATA_PATH / \"multi_fast5_zip_v4.pod5\"\nREAD_IDS_PATH = TEST_DATA_PATH / \"subset_mapping_examples/read_ids.txt\"\n\nEXPECTED_READ_IDS = [\n    \"0000173c-bf67-44e7-9a9c-1ad0bc728e74\",\n    \"00925f34-6baf-47fc-b40c-22591e27fb5c\",\n]\n\n\nclass TestFilterParseIds:\n    \"\"\"Test that pod5 filter parse ids\"\"\"\n\n    def _read_ids_path(self, tmp_path: Path, ids: List[str]) -> Path:\n        rids = tmp_path / \"read_ids.txt\"\n        rids.write_text(\"\\n\".join(ids))\n        return rids\n\n    def _assert_columns(self, df: pl.LazyFrame) -> None:\n        assert PL_READ_ID in df.collect_schema().names()\n        assert PL_DEST_FNAME in df.collect_schema().names()\n\n    def _assert_all_expected(self, df: pl.LazyFrame) -> None:\n        read_ids = df.select(PL_READ_ID).collect().to_series().to_list()\n        assert len(read_ids) > 0\n        assert len(read_ids) == len(EXPECTED_READ_IDS)\n        assert set(read_ids) == set(EXPECTED_READ_IDS)\n\n    def test_example(self, tmp_path: Path) -> None:\n        \"\"\"Test known good example to filter\"\"\"\n        path = self._read_ids_path(tmp_path, EXPECTED_READ_IDS)\n        assert len(path.read_text().splitlines()) == 2\n        df = parse_read_id_targets(path, Path.cwd())\n        self._assert_columns(df)\n        self._assert_all_expected(df)\n\n    def test_example_with_header(self, tmp_path: Path) -> None:\n        \"\"\"Test known good example to filter\"\"\"\n        data = [\"read_id\"]\n        data.extend(EXPECTED_READ_IDS)\n        path = self._read_ids_path(tmp_path, data)\n        assert len(path.read_text().splitlines()) == 3\n\n        df = parse_read_id_targets(path, Path.cwd())\n        self._assert_columns(df)\n        self._assert_all_expected(df)\n\n    def test_example_with_comments(self, tmp_path: Path) -> None:\n        \"\"\"Test known good example to filter\"\"\"\n        data = [\"read_id\", \"#\", \"# A comment read_id\"]\n        data.extend(EXPECTED_READ_IDS)\n        data.extend([\"# Comment\"])\n        path = self._read_ids_path(tmp_path, data)\n        assert len(path.read_text().splitlines()) == 6\n\n        df = parse_read_id_targets(path, Path.cwd())\n        self._assert_columns(df)\n        self._assert_all_expected(df)\n\n    def test_example_with_whitespace(self, tmp_path: Path) -> None:\n        \"\"\"Test known good example to filter\"\"\"\n        data = [\"read_id\", \"#\", \"# A comment read_id\", \" \"]\n        data.extend(EXPECTED_READ_IDS)\n        data.extend([\"# Comment\", \" \"])\n        path = self._read_ids_path(tmp_path, data)\n        assert len(path.read_text().splitlines()) == 8\n\n        df = parse_read_id_targets(path, Path.cwd())\n        self._assert_columns(df)\n        self._assert_all_expected(df)\n\n    def test_no_ids(self, tmp_path: Path) -> None:\n        \"\"\"Test known good example to filter\"\"\"\n        data = [\"read_id\", \"#\", \"# A comment read_id\"]\n        path = self._read_ids_path(tmp_path, data)\n        assert len(path.read_text().splitlines()) == 3\n\n        with pytest.raises(AssertionError, match=\"Found 0 read_ids\"):\n            parse_read_id_targets(path, Path.cwd())\n\n\nclass TestFilter:\n    def test_all_in_out(self, tmp_path: Path) -> None:\n        \"\"\"Parse a pod5 file for it's all read_ids and filter expecting all in output\"\"\"\n        with p5.Reader(POD5_PATH) as reader:\n            all_ids = reader.read_ids\n\n        output = tmp_path / \"output.pod5\"\n        read_ids = tmp_path / \"read_ids.txt\"\n        with read_ids.open(\"w\") as _fh:\n            _fh.write(\"\\n\".join(all_ids))\n\n        assert len(all_ids) > 0\n        targets = parse_read_id_targets(read_ids, output)\n        assert isinstance(targets, pl.LazyFrame)\n        targets = targets.collect()\n        assert len(targets) == len(all_ids) == 10\n        assert set(targets.get_column(PL_READ_ID)) == set(all_ids)\n\n        filter_pod5(\n            [POD5_PATH],\n            output,\n            read_ids,\n            missing_ok=False,\n            force_overwrite=False,\n            recursive=False,\n        )\n\n        assert output.is_file()\n        with p5.Reader(output) as reader:\n            assert all_ids == reader.read_ids\n            assert set(reader.read_ids) == set(targets.get_column(PL_READ_ID))\n            assert reader.num_reads > 0\n\n    def test_no_duplicates(self, tmp_path: Path) -> None:\n        \"\"\"Provide duplicate read_ids in the input checking no duplicates are written\"\"\"\n        with p5.Reader(POD5_PATH) as reader:\n            first_id = reader.read_ids[0]\n\n        output = tmp_path / \"output.pod5\"\n        read_ids = tmp_path / \"read_ids.txt\"\n        with read_ids.open(\"w\") as _fh:\n            _fh.write(\"\\n\".join([first_id, first_id]))\n\n        targets = parse_read_id_targets(read_ids, output)\n        assert isinstance(targets, pl.LazyFrame)\n        targets = targets.collect()\n        assert len(targets) == 1\n        assert set(targets.get_column(PL_READ_ID)) == set([first_id])\n\n        filter_pod5(\n            [POD5_PATH],\n            output,\n            read_ids,\n            missing_ok=False,\n            force_overwrite=False,\n            recursive=False,\n        )\n\n        assert output.is_file()\n        with p5.Reader(output) as reader:\n            assert reader.read_ids[0] == first_id\n            assert reader.num_reads == 1\n\n    def test_missing_read_ids(self, tmp_path: Path) -> None:\n        \"\"\"Assert that missing read_ids are detected\"\"\"\n        with p5.Reader(POD5_PATH) as reader:\n            all_ids = reader.read_ids\n\n        # Write a known missing read_id\n        read_ids = tmp_path / \"read_ids.txt\"\n        with read_ids.open(\"w\") as _fh:\n            _fh.write(\"\\n\".join(all_ids))\n            _fh.write(f\"\\n{UUID(bytes=b'0'*16)}\\n\")\n\n        output = tmp_path / \"output.pod5\"\n        with pytest.raises(RuntimeError):\n            filter_pod5(\n                [POD5_PATH],\n                output,\n                read_ids,\n                missing_ok=False,\n                force_overwrite=False,\n                recursive=False,\n            )\n        assert not output.exists()\n\n        filter_pod5(\n            [POD5_PATH],\n            output,\n            read_ids,\n            missing_ok=True,\n            force_overwrite=False,\n            recursive=False,\n        )\n        assert output.exists()\n\n    def test_force_overwrite(self, tmp_path: Path) -> None:\n        \"\"\"Assert we cannot overwrite outputs without force\"\"\"\n\n        output = tmp_path / \"output.pod5\"\n        output.touch()\n        with pytest.raises(FileExistsError, match=\"--force-overwrite not set\"):\n            filter_pod5(\n                [POD5_PATH],\n                output,\n                READ_IDS_PATH,\n                missing_ok=False,\n                force_overwrite=False,\n                recursive=False,\n            )\n\n        assert output.exists()\n        filter_pod5(\n            [POD5_PATH],\n            output,\n            READ_IDS_PATH,\n            missing_ok=False,\n            force_overwrite=True,\n            recursive=False,\n        )\n        assert output.exists()\n        with p5.Reader(output) as reader:\n            assert reader.num_reads\n\n    def test_empty_input_fails(self, tmp_path: Path) -> None:\n        \"\"\"Request zero reads\"\"\"\n\n        empty_file = tmp_path / \"empty.txt\"\n        empty_file.touch()\n        output = tmp_path / \"output.pod5\"\n\n        with pytest.raises(pl.NoDataError, match=\"empty CSV\"):\n            filter_pod5(\n                [POD5_PATH],\n                output,\n                empty_file,\n                missing_ok=False,\n                force_overwrite=False,\n                recursive=False,\n            )\n\n    @pytest.mark.parametrize(\"n_reads\", [10, 50])\n    def test_random_inputs(self, pod5_factory, tmp_path: Path, n_reads: int) -> None:\n        p25 = pod5_factory(25)\n        p50 = pod5_factory(50)\n        p100 = pod5_factory(100)\n        pod5s = [p25, p50, p100]\n        view_tsv = tmp_path / \"view.tsv\"\n        view_pod5(\n            pod5s, view_tsv, include=\"read_id\", no_header=True, force_overwrite=True\n        )\n\n        read_ids = sample(list(set(view_tsv.read_text().splitlines())), k=n_reads)\n        filter_path = tmp_path / \"filter.txt\"\n        filter_path.write_text(\"\\n\".join(read_ids))\n        output = tmp_path / \"output.pod5\"\n        filter_pod5(\n            pod5s,\n            output,\n            ids=filter_path,\n            missing_ok=False,\n            force_overwrite=False,\n            recursive=False,\n        )\n\n        with p5.Reader(output) as reader:\n            assert len(reader.read_ids) == n_reads\n\n    def test_input_directory(self, tmp_path: Path) -> None:\n        \"\"\"Take inputs from directory\"\"\"\n        output = tmp_path / \"output.pod5\"\n        copy_of = tmp_path / \"input.pod5\"\n        copy_of.write_bytes(POD5_PATH.read_bytes())\n\n        filter_pod5(\n            [tmp_path],\n            output,\n            READ_IDS_PATH,\n            missing_ok=False,\n            force_overwrite=False,\n            recursive=False,\n        )\n\n        assert output.exists()\n        with p5.Reader(output) as reader:\n            assert reader.num_reads\n\n    @skip_if_windows\n    def test_recursive_inputs_symlink(self, tmp_path: Path) -> None:\n        \"\"\"Take inputs from directory\"\"\"\n        output = tmp_path / \"output.pod5\"\n\n        subdir = tmp_path / \"one/two\"\n        subdir.mkdir(parents=True)\n        subdir_symlink = subdir / \"input.pod5\"\n        subdir_symlink.symlink_to(POD5_PATH)\n\n        filter_pod5(\n            [tmp_path],\n            output,\n            READ_IDS_PATH,\n            missing_ok=False,\n            force_overwrite=False,\n            recursive=True,\n        )\n\n        assert output.exists()\n        with p5.Reader(output) as reader:\n            assert reader.num_reads\n"
  },
  {
    "path": "python/pod5/src/tests/test_inspect.py",
    "content": "from pathlib import Path\nimport pytest\n\nfrom pod5.tools.pod5_inspect import inspect_pod5\n\n\nTEST_DATA_PATH = Path(__file__).parent.parent.parent.parent.parent / \"test_data\"\nPOD5_PATH = TEST_DATA_PATH / \"multi_fast5_zip_v4.pod5\"\n\n\nclass TestReads:\n    def test_reads_header_written_once(\n        self, capsys: pytest.CaptureFixture, pod5_factory\n    ) -> None:\n        \"\"\"Assert that the header line in pod5 inspect reads is only written once\"\"\"\n        paths = [pod5_factory(10), pod5_factory(25)]\n\n        inspect_pod5(\"reads\", paths)\n\n        lines = str(capsys.readouterr().out).splitlines()\n        assert len(lines) == 1 + 10 + 25\n        assert sum(\"read_id\" in line for line in lines) == 1\n"
  },
  {
    "path": "python/pod5/src/tests/test_merge.py",
    "content": "from pathlib import Path\n\nimport pytest\n\nimport pod5 as p5\nfrom pod5.tools.pod5_merge import merge_pod5\n\nTEST_DATA_PATH = Path(__file__).parent.parent.parent.parent.parent / \"test_data\"\n\n\nclass TestMerge:\n    \"\"\"Test merge application\"\"\"\n\n    def test_merge_runs(self, tmp_path: Path):\n        \"\"\"Test that the merge tool runs a trivial example\"\"\"\n\n        # Test all pod5 inputs in test data, which will likely contain duplicates\n        inputs = list(TEST_DATA_PATH.glob(\"split_*pod5\"))\n        output = tmp_path / \"test.pod5\"\n        merge_pod5(\n            inputs[:2],\n            output,\n            force_overwrite=False,\n            recursive=False,\n        )\n\n        assert output.exists()\n\n        with p5.Reader(output) as reader:\n            reads = list(reader.reads())\n            assert reads\n\n    def test_merge_duplicate_stopped(self, tmp_path: Path):\n        \"\"\"Test that the merge tool prevents duplicate reads being merged\"\"\"\n\n        # Test all pod5 inputs in test data, which will likely contain duplicates\n        inputs = list(TEST_DATA_PATH.glob(\"*pod5\"))\n        output = tmp_path / \"test.pod5\"\n\n        with pytest.raises(RuntimeError, match=\"Duplicate read id\"):\n            merge_pod5(inputs, output)\n"
  },
  {
    "path": "python/pod5/src/tests/test_reader.py",
    "content": "\"\"\"\nTesting Pod5Reader\n\"\"\"\n\nimport random\nfrom pathlib import Path\nfrom typing import Type\nfrom unittest import mock\nfrom uuid import UUID, uuid4\n\nimport lib_pod5 as p5b\nimport numpy\nimport numpy.typing\nimport packaging\nimport pyarrow as pa\nimport pytest\n\nimport pod5 as p5\nfrom pod5.api_utils import format_read_ids\nfrom pod5.pod5_types import Calibration, EndReason, RunInfo\nfrom pod5.reader import ArrowTableHandle, ReadRecordBatch, SignalRowInfo\nfrom tests.conftest import POD5_PATH\n\n\nclass TestPod5Reader:\n    \"\"\"Test the Pod5Reader from a pod5 file\"\"\"\n\n    def test_reader_fixture(self, reader: p5.Reader) -> None:\n        \"\"\"Basic assertions on the reader fixture\"\"\"\n        assert isinstance(reader, p5.Reader)\n        assert isinstance(reader.batch_count, int)\n        assert reader.is_vbz_compressed is True\n        assert reader.batch_count > 0\n\n    @pytest.mark.parametrize(\n        \"attribute,expected_type\",\n        [\n            (\"calibration\", Calibration),\n            (\"calibration_digitisation\", int),\n            (\"calibration_range\", float),\n            (\"end_reason\", EndReason),\n            (\"read_id\", UUID),\n            (\"read_number\", int),\n            (\"start_sample\", int),\n            (\"median_before\", float),\n            (\"run_info\", RunInfo),\n            (\"num_minknow_events\", int),\n            (\"num_reads_since_mux_change\", int),\n            (\"num_samples\", int),\n        ],\n    )\n    def test_reader_reads_types(\n        self, reader: p5.Reader, attribute: str, expected_type: Type\n    ) -> None:\n        \"\"\"Assert the types returned for reads are consistent with expectations\"\"\"\n        minimum_reads = 5\n        for pod5_read in reader.reads():\n            assert isinstance(pod5_read, p5.ReadRecord)\n            assert isinstance(getattr(pod5_read, attribute), expected_type)\n            minimum_reads -= 1\n            if minimum_reads <= 0:\n                break\n        else:\n            assert False, \"did not test minimum reads!\"\n\n    @pytest.mark.parametrize(\n        \"attribute,collection_type,dtype\",\n        [\n            (\"signal\", numpy.ndarray, numpy.int16),\n            (\"signal_pa\", numpy.ndarray, numpy.float32),\n        ],\n    )\n    def test_reader_reads_numpy_types(\n        self,\n        reader: p5.Reader,\n        attribute: str,\n        collection_type: Type,\n        dtype: numpy.typing.DTypeLike,\n    ) -> None:\n        \"\"\"Assert the types returned for reads are consistent with expectations\"\"\"\n        minimum_reads = 5\n        for pod5_read in reader.reads():\n            assert isinstance(pod5_read, p5.ReadRecord)\n            collection = getattr(pod5_read, attribute)\n            assert isinstance(collection, collection_type)\n            assert collection.dtype == dtype\n\n            minimum_reads -= 1\n            if minimum_reads <= 0:\n                break\n        else:\n            assert False, \"did not test minimum reads!\"\n\n    @pytest.mark.parametrize(\n        \"attribute,collection_type,leaf_type\",\n        [\n            (\"signal_rows\", list, SignalRowInfo),\n        ],\n    )\n    def test_reader_reads_container_types(\n        self,\n        reader: p5.Reader,\n        attribute: str,\n        collection_type: Type,\n        leaf_type: Type,\n    ) -> None:\n        \"\"\"Assert the types returned for reads are consistent with expectations\"\"\"\n        minimum_reads = 5\n        for pod5_read in reader.reads():\n            assert isinstance(pod5_read, p5.ReadRecord)\n            collection = getattr(pod5_read, attribute)\n            assert isinstance(collection, collection_type)\n            assert isinstance(collection[0], leaf_type)\n            assert isinstance(collection[-1], leaf_type)\n\n            minimum_reads -= 1\n            if minimum_reads <= 0:\n                break\n        else:\n            assert False, \"did not test minimum reads!\"\n\n    def test_attribute_types(self) -> None:\n        with p5.Reader(POD5_PATH) as reader:\n            assert isinstance(reader.path, Path)\n            assert reader.path == POD5_PATH\n            assert reader.reads_table_version == 4\n\n            # File handles\n            assert isinstance(reader.inner_file_reader, p5b.Pod5FileReader)\n            assert isinstance(reader.read_table, pa.ipc.RecordBatchFileReader)\n            assert isinstance(reader.run_info_table, pa.ipc.RecordBatchFileReader)\n            assert isinstance(reader.signal_table, pa.ipc.RecordBatchFileReader)\n\n            assert isinstance(reader.file_version, packaging.version.Version)\n            assert isinstance(\n                reader.file_version_pre_migration, packaging.version.Version\n            )\n            assert isinstance(reader.writing_software, str)\n            assert isinstance(reader.file_identifier, UUID)\n            assert isinstance(reader.reads_table_version, int)\n            assert isinstance(reader.is_vbz_compressed, bool)\n            assert isinstance(reader.signal_batch_row_count, int)\n            assert isinstance(reader.batch_count, int)\n            assert isinstance(reader.num_reads, int)\n\n            assert isinstance(reader.read_ids_raw, pa.ChunkedArray)\n            assert isinstance(reader.read_ids, list)\n            assert all(isinstance(r, str) for r in reader.read_ids)\n\n            assert isinstance(reader.get_batch(0), ReadRecordBatch)\n\n    def test_without_mmap(self) -> None:\n        \"\"\"Test the file load without mmap for low-memory devices\"\"\"\n        pod5_file_reader = p5b.open_file(str(POD5_PATH))\n        read_table_location = pod5_file_reader.get_file_read_table_location()\n\n        # Raise OSerror when loading with mmap\n        mocked = ArrowTableHandle\n        mocked._open_reader_with_mmap = mock.Mock(side_effect=OSError)  # type: ignore\n        ath = ArrowTableHandle(read_table_location)\n\n        # Assert no handles are opened promptly\n        assert ath.reader is not None\n        assert ath.reader.num_record_batches > 0\n\n        # Clean reader resources\n        del pod5_file_reader\n\n    def test_sparse_file_warns_during_preflight(self, tmp_path: Path, capfd) -> None:\n        \"\"\"Assert sparse/offloaded-like files emit a preflight warning.\"\"\"\n        sparse_path = tmp_path / \"sparse-warning.pod5\"\n        sparse_size_bytes = 128 * 1024 * 1024\n        with sparse_path.open(\"wb\") as sparse_file:\n            sparse_file.seek(sparse_size_bytes - 1)\n            sparse_file.write(b\"\\0\")\n\n        sparse_stats = sparse_path.stat()\n        if not hasattr(sparse_stats, \"st_blocks\"):\n            pytest.skip(\"filesystem does not expose st_blocks\")\n\n        allocated_bytes = sparse_stats.st_blocks * 512\n        missing_fraction = 1.0 - (allocated_bytes / sparse_stats.st_size)\n        if missing_fraction < 0.8:\n            pytest.skip(\"test file is not sparse enough on this filesystem\")\n\n        with pytest.raises(RuntimeError):\n            p5b.open_file(str(sparse_path))\n\n        captured = capfd.readouterr()\n        assert \"Warning: POD5 file\" in captured.err\n        assert \"has st_size=\" in captured.err\n        assert (\n            \"The file may be sparse or offloaded and open/read operations may fail.\"\n            in captured.err\n        )\n\n    def test_iter_selection_in_file_order(self, reader: p5.Reader) -> None:\n        \"\"\"Tests iteration order is on-disk order\"\"\"\n        shuffled = reader.read_ids\n        random.shuffle(shuffled)\n        observed_count = 0\n        for record, read_id in zip(reader.reads(selection=shuffled), reader.read_ids):\n            assert str(record.read_id) == read_id\n            observed_count += 1\n        assert observed_count == len(reader.read_ids)\n\n\nclass TestRecordBatch:\n    def test_get_read(self, pod5_factory) -> None:\n        n_reads = 10\n        path = pod5_factory(n_reads)\n        with p5.Reader(path) as reader:\n            reads = list(reader.reads())\n\n            assert reader.batch_count == 1\n            batch = reader.get_batch(0)\n            assert batch.num_reads == n_reads\n\n            for idx, read in enumerate(reads):\n                assert read.read_id == batch.get_read(idx).read_id\n            assert n_reads == idx + 1\n\n    def test_column_selection(self, pod5_factory) -> None:\n        n_reads = 10\n        path = pod5_factory(n_reads)\n        with p5.Reader(path) as reader:\n            batch = reader.get_batch(0)\n\n            assert len(batch.read_id_column) == n_reads\n            assert len(batch.read_number_column) == n_reads\n\n            select_idxs = [3, 4, 8]\n            batch.set_selected_batch_rows(select_idxs)\n\n            assert type(batch.read_id_column) is pa.FixedSizeBinaryArray\n            assert len(batch.read_id_column) == len(select_idxs)\n            ids = [reader.read_ids[idx] for idx in select_idxs]\n            assert format_read_ids(batch.read_id_column) == ids\n\n            assert type(batch.read_number_column) is pa.UInt32Array\n            assert len(batch.read_number_column) == len(select_idxs)\n            reads = list(reader.reads())\n            rnums = [reads[idx].read_number for idx in select_idxs]\n\n            # assert type(batch.read_number_column.to_numpy().tolist()) == list\n            assert batch.read_number_column.to_numpy().tolist() == rnums\n\n    def test_read_batches(self, pod5_factory) -> None:\n        n_reads = 1100\n        path = pod5_factory(n_reads)\n        with p5.Reader(path) as reader:\n            rrb = reader.read_batches\n            assert len(list(rrb())) == 2\n            assert len(list(rrb(batch_selection=[0]))) == 1\n            assert len(list(rrb(batch_selection=[0, 1]))) == 2\n\n    def test_read_batches_raises(self, pod5_factory) -> None:\n        n_reads = 1100\n        path = pod5_factory(n_reads)\n        with p5.Reader(path) as reader:\n            # with pytest.raises(AssertionError):\n            first, last = reader.read_ids[0], reader.read_ids[-1]\n            with pytest.raises(ValueError, match=\"mutually exclusive\"):\n                list(reader.read_batches(selection=[first, last], batch_selection=[0]))\n\n            with pytest.raises(RuntimeError, match=\"Failed to find\"):\n                list(reader.read_batches(selection=[str(uuid4())]))\n\n    def test_cache_exceptions(self, pod5_factory) -> None:\n        n_reads = 10\n        path = pod5_factory(n_reads)\n        with p5.Reader(path) as reader:\n            batch = reader.get_batch(0)\n\n            # No cache set\n            with pytest.raises(RuntimeError, match=\"No cached signal data available\"):\n                batch.cached_sample_count_column\n            with pytest.raises(RuntimeError, match=\"No cached signal data available\"):\n                batch.cached_samples_column\n"
  },
  {
    "path": "python/pod5/src/tests/test_recover.py",
    "content": "import sys\nfrom os.path import exists\nfrom pathlib import Path\n\nimport numpy as np\n\nimport pytest\n\nimport pod5 as p5\nfrom pod5.tools.pod5_recover import recover_pod5\n\nfrom tests.conftest import _random_read\n\nTEST_DATA_PATH = Path(__file__).parent.parent.parent.parent.parent / \"test_data\"\n\n\nclass TestRecover:\n    \"\"\"Test recover application\"\"\"\n\n    def _generate_recoverable_file(self, dest_path: Path, read_count=1200):\n        reads = []\n        with p5.Writer(dest_path) as writer:\n            for _ in range(read_count):\n                read = _random_read()\n                reads.append(read)\n                writer.add_read(read)\n\n            # Prevent close being called, by keeping a ref to the writer\n            self._tmp_file_ref = writer._writer\n            # And preventing the p5.Writer from closing it:\n            writer._writer = None\n\n        # Check the file is left as tmp:\n        assert dest_path.exists()\n        assert len(list(dest_path.parent.glob(\".*.tmp-run-info\"))) == 1\n        assert len(list(dest_path.parent.glob(\".*.tmp-reads\"))) == 1\n        return reads\n\n    @pytest.mark.parametrize(\"cleanup\", [False, True])\n    def test_recover_runs(\n        self, capsys: pytest.CaptureFixture, tmp_path: Path, cleanup: bool\n    ):\n        \"\"\"Test that the recover tool runs a trivial example\"\"\"\n\n        recoverable_path = tmp_path / \"recoverable.tmp\"\n        added_reads = self._generate_recoverable_file(recoverable_path)\n\n        with pytest.raises(RuntimeError):\n            p5.Reader(recoverable_path)\n\n        recover_pod5(\n            [recoverable_path], recursive=False, force_overwrite=False, cleanup=cleanup\n        )\n\n        expected_recovered_path = recoverable_path.parent / (\n            recoverable_path.stem + \"_recovered.pod5\"\n        )\n\n        with p5.Reader(expected_recovered_path) as reader:\n            count_recovered = 0\n            for read_record, expected_read in zip(reader.reads(), added_reads):\n                assert read_record.read_id == expected_read.read_id\n                assert read_record.end_reason == expected_read.end_reason\n                assert read_record.pore == expected_read.pore\n                assert read_record.run_info == expected_read.run_info\n                assert np.array_equal(read_record.signal, expected_read.signal)\n                count_recovered += 1\n\n            # Only recover in whole batches, so whole 1000 read counts\n            expected_recovered_count = (len(added_reads) // 1000) * 1000\n            assert count_recovered == expected_recovered_count\n\n        if sys.platform == \"win32\":\n            # Cleanup errors are expected on Windows, because we are still holding input file handles.\n            assert exists(recoverable_path)\n            if cleanup:\n                capture_result = capsys.readouterr()\n                assert (\n                    \"remove: The process cannot access the file because it is being used by another process.\"\n                    in capture_result.out\n                )\n        else:\n            assert exists(recoverable_path) ^ cleanup\n"
  },
  {
    "path": "python/pod5/src/tests/test_repack.py",
    "content": "from pathlib import Path\nimport random\nfrom uuid import uuid4\nimport numpy as np\n\nimport pod5 as p5\nfrom pod5.repack import Repacker\nfrom pod5.tools.pod5_repack import repack_pod5\nfrom tests.conftest import skip_if_windows\nimport pytest\n\n\nTEST_DATA_PATH = Path(__file__).parent.parent.parent.parent.parent / \"test_data\"\nPOD5_PATH = TEST_DATA_PATH / \"multi_fast5_zip_v4.pod5\"\n\n\nclass TestRepack:\n    \"\"\"Test that pod5 repack runs\"\"\"\n\n    def test_works(self, tmp_path: Path) -> None:\n        data = tmp_path / \"subdir/test.pod5\"\n        data.parent.mkdir(exist_ok=False, parents=True)\n        data.write_bytes(POD5_PATH.read_bytes())\n\n        output = tmp_path / \"output\"\n        assert not (output / \"test.pod5\").exists()\n        repack_pod5(\n            [tmp_path], output, threads=2, force_overwrite=False, recursive=True\n        )\n        assert output.is_dir()\n        assert (output / \"test.pod5\").is_file()\n\n        with p5.Reader(output / \"test.pod5\") as dest:\n            with p5.Reader(data) as source:\n                for d_read, s_read in zip(dest, source):\n                    assert d_read.read_id == s_read.read_id\n                    assert d_read.pore == s_read.pore\n                    assert d_read.calibration == s_read.calibration\n                    assert np.array_equal(d_read.signal, s_read.signal)\n\n    def test_detect_name_collision(self, tmp_path: Path) -> None:\n        data = tmp_path / \"subdir/test.pod5\"\n        data.parent.mkdir(exist_ok=False, parents=True)\n        data.write_bytes(POD5_PATH.read_bytes())\n\n        similar_name = tmp_path / \"test.pod5\"\n        similar_name.touch()\n\n        output = tmp_path / \"output\"\n        with pytest.raises(ValueError, match=\"same filename\"):\n            repack_pod5(\n                [tmp_path], output, threads=2, force_overwrite=False, recursive=True\n            )\n\n    @skip_if_windows\n    def test_overwrite_symlink(self, tmp_path: Path) -> None:\n        symlink = tmp_path / \"subdir/test.pod5\"\n        symlink.parent.mkdir(exist_ok=False, parents=True)\n        symlink.symlink_to(POD5_PATH)\n\n        dest = tmp_path / \"test.pod5\"\n        dest.touch()\n\n        with pytest.raises(FileExistsError, match=\"Refusing to overwrite\"):\n            repack_pod5(\n                [tmp_path / \"subdir\"],\n                tmp_path,\n                threads=2,\n                force_overwrite=False,\n                recursive=True,\n            )\n\n        repack_pod5(\n            [tmp_path / \"subdir\"],\n            tmp_path,\n            threads=2,\n            force_overwrite=True,\n            recursive=True,\n        )\n\n        assert dest.is_file()\n        with p5.Reader(dest) as reader:\n            assert reader.read_ids\n\n    def test_overwrite_data(self, tmp_path: Path) -> None:\n        data = tmp_path / \"subdir/test.pod5\"\n        data.parent.mkdir(exist_ok=False, parents=True)\n        data.write_bytes(POD5_PATH.read_bytes())\n\n        dest = tmp_path / \"test.pod5\"\n        dest.touch()\n\n        repack_pod5(\n            [tmp_path / \"subdir\"],\n            tmp_path,\n            threads=2,\n            force_overwrite=True,\n            recursive=True,\n        )\n\n        assert dest.is_file()\n        with p5.Reader(dest) as reader:\n            assert reader.read_ids\n\n\nclass TestRepacker:\n    def test_add_all(self, tmp_path: Path, pod5_factory) -> None:\n        path = pod5_factory(10)\n\n        dest = tmp_path / \"dest.pod5\"\n        repacker = Repacker()\n        with p5.Writer(dest) as writer:\n            output = repacker.add_output(writer, check_duplicate_read_ids=True)\n\n            assert repacker.reads_requested == 0\n            assert repacker.reads_completed == 0\n            assert (\n                not repacker.is_complete\n            )  # Output not marked finished, so not complete\n\n            with p5.Reader(path) as reader:\n                repacker.add_all_reads_to_output(output, reader)\n            repacker.set_output_finished(output)\n            repacker.finish()\n\n            assert repacker.reads_requested == 10\n            assert repacker.reads_completed == 10\n            assert repacker.is_complete\n\n    def test_add_selection(self, tmp_path: Path, pod5_factory) -> None:\n        path = pod5_factory(1100)\n\n        dest = tmp_path / \"dest.pod5\"\n        repacker = Repacker()\n        with p5.Writer(dest) as writer:\n            output = repacker.add_output(writer)\n\n            with p5.Reader(path) as reader:\n                selection = set(random.sample(reader.read_ids, 50))\n                repacker.add_selected_reads_to_output(output, reader, selection)\n\n            repacker.set_output_finished(output)\n            repacker.finish()\n\n            assert repacker.reads_requested == len(selection)\n            assert repacker.reads_completed == len(selection)\n            assert repacker.is_complete\n\n        with p5.Reader(dest) as confirm:\n            assert set(confirm.read_ids) == set(selection)\n\n        repacker.finish()\n\n    def test_missing_selection(self, tmp_path: Path, pod5_factory) -> None:\n        path = pod5_factory(10)\n\n        dest = tmp_path / \"dest.pod5\"\n        repacker = Repacker()\n        with p5.Writer(dest) as writer:\n            output = repacker.add_output(writer)\n\n            with p5.Reader(path) as reader:\n                selection = set([str(uuid4())])\n                with pytest.raises(RuntimeError, match=\"Failed to find\"):\n                    repacker.add_selected_reads_to_output(output, reader, selection)\n\n            repacker.set_output_finished(output)\n            repacker.finish()\n"
  },
  {
    "path": "python/pod5/src/tests/test_signal_tools.py",
    "content": "\"\"\"\nTesting signal_tools\n\"\"\"\n\nfrom io import TextIOWrapper\nfrom pathlib import Path\nimport random\n\nimport numpy as np\nimport numpy.typing as npt\nfrom pod5.api_utils import safe_close\nimport pytest\n\nfrom pod5.signal_tools import (\n    vbz_compress_signal,\n    vbz_compress_signal_chunked,\n    vbz_decompress_signal,\n    vbz_decompress_signal_chunked,\n)\n\nTEST_SEEDS = range(10)\n\n\nclass TestPod5SignalTools:\n    \"\"\"Test the POD5 signal_tools module\"\"\"\n\n    @pytest.mark.parametrize(\"random_signal\", TEST_SEEDS, indirect=True)\n    def test_round_trip(self, random_signal: npt.NDArray[np.int16]) -> None:\n        \"\"\"Test compression and decompression round-trip\"\"\"\n\n        sample_count = random_signal.shape[0]\n        round_trip_signal = vbz_decompress_signal(\n            vbz_compress_signal(random_signal), sample_count\n        )\n        assert np.array_equal(round_trip_signal, random_signal)\n\n    def test_round_trip_empty(self) -> None:\n        \"\"\"Test compression and decompression round-trip of empty signal data\"\"\"\n        empty_signal = np.array([], dtype=np.int16)\n        sample_count = empty_signal.shape[0]\n        round_trip_signal = vbz_decompress_signal(\n            vbz_compress_signal(empty_signal), sample_count\n        )\n        assert np.array_equal(round_trip_signal, empty_signal)\n\n    @pytest.mark.parametrize(\"random_signal\", TEST_SEEDS, indirect=True)\n    def test_round_trip_chunked(self, random_signal: npt.NDArray[np.int16]) -> None:\n        \"\"\"Test compression and decompression round-trip for chunked data\"\"\"\n\n        sample_count = random_signal.shape[0]\n        chunk_size = random.randint(1, 1000)\n\n        compressed_signal_chunked, signal_chunk_lengths = vbz_compress_signal_chunked(\n            random_signal, chunk_size\n        )\n\n        assert len(compressed_signal_chunked) == len(signal_chunk_lengths)\n        assert sample_count == sum(signal_chunk_lengths)\n\n        uncompressed_signal = vbz_decompress_signal_chunked(\n            compressed_signal_chunked, signal_chunk_lengths\n        )\n\n        assert np.array_equal(uncompressed_signal, random_signal)\n\n    def test_round_trip_chunked_empty(self) -> None:\n        \"\"\"Test compression and decompression round-trip for empty chunked data\"\"\"\n        empty_signal = np.array([], dtype=np.int16)\n        sample_count = empty_signal.shape[0]\n        chunk_size = random.randint(1, 1000)\n\n        compressed_signal_chunked, signal_chunk_lengths = vbz_compress_signal_chunked(\n            empty_signal, chunk_size\n        )\n\n        assert len(compressed_signal_chunked) == len(signal_chunk_lengths)\n        assert sample_count == sum(signal_chunk_lengths)\n\n        uncompressed_signal = vbz_decompress_signal_chunked(\n            compressed_signal_chunked, signal_chunk_lengths\n        )\n\n        assert np.array_equal(uncompressed_signal, empty_signal)\n\n\nclass DemoObj:\n    def __init__(self, path: Path) -> None:\n        self.handle: TextIOWrapper = path.open(\"r\")\n        self.other: str = \"other\"\n\n    def __del__(self):\n        safe_close(self, \"handle\")\n\n\n@pytest.fixture\ndef demo_obj(tmp_path: Path) -> DemoObj:\n    path = tmp_path / \"example.txt\"\n    path.touch()\n    return DemoObj(path)\n\n\nclass TestSafeClose:\n    \"\"\"Test the safe_close utility\"\"\"\n\n    def test_closes(self, demo_obj: DemoObj) -> None:\n        \"\"\"Given a file handle assert it's closed\"\"\"\n        assert not demo_obj.handle.closed\n        safe_close(demo_obj, \"handle\")\n        assert demo_obj.handle.closed\n\n    def test_passes_unknown_attribute(self, demo_obj: DemoObj) -> None:\n        \"\"\"Given a file handle assert it's closed\"\"\"\n        safe_close(demo_obj, \"not_an_attr\")\n        safe_close(demo_obj, \"\")\n\n    def test_passes_known_non_handle_attribute(self, demo_obj: DemoObj) -> None:\n        \"\"\"Given a file handle assert it's closed\"\"\"\n        assert demo_obj.other == \"other\"\n        safe_close(demo_obj, \"other\")\n        assert demo_obj.other == \"other\"\n"
  },
  {
    "path": "python/pod5/src/tests/test_subset.py",
    "content": "import subprocess\nimport sys\nfrom pathlib import Path\nfrom types import ModuleType\nfrom typing import Dict, List, Set\n\nimport lib_pod5 as p5b\nimport polars as pl\nimport pytest\nfrom polars.testing import assert_frame_equal, assert_series_equal\n\nimport pod5\nfrom pod5.tools.pod5_inspect import inspect_pod5\nfrom pod5.tools.pod5_subset import (\n    PL_DEST_FNAME,\n    PL_READ_ID,\n    assert_filename_template,\n    build_targets_dict,\n    column_keys_from_template,\n    create_default_filename_template,\n    fstring_to_polars,\n    get_separator,\n    parse_csv_mapping,\n    parse_table_mapping,\n    subset_pod5,\n)\n\n\ndef get_resource_module() -> ModuleType | None:\n    # resource module only available in posix platforms\n    # This module is used to set the resource limits when testing subset's\n    # file handle / resource management\n    try:\n        import resource\n\n        return resource\n    except ModuleNotFoundError:\n        return None\n\n\nHAS_RLIMIT = hasattr(get_resource_module(), \"RLIMIT_NOFILE\")\n\nCSV_RESULT_1 = {\n    \"repeated_name\": {\"r1\", \"r2\"},\n    \"multi_read\": {\"r1\", \"r2\", \"r3\"},\n    \"handle_spaces\": {\"r2\", \"r3\", \"r5\"},\n}\n\nMAPPING = {\n    \"well-2.pod5\": {\n        \"002fde30-9e23-4125-9eae-d112c18a81a7\",\n    },\n    \"well-4.pod5\": {\n        \"00919556-e519-4960-8aa5-c2dfa020980c\",\n        \"0000173c-bf67-44e7-9a9c-1ad0bc728e74\",\n        \"00925f34-6baf-47fc-b40c-22591e27fb5c\",\n    },\n}\n\nMAPPING_REPEATED = {\n    \"well-2.pod5\": {\n        \"002fde30-9e23-4125-9eae-d112c18a81a7\",\n        \"00925f34-6baf-47fc-b40c-22591e27fb5c\",\n    },\n    \"well-4.pod5\": {\n        \"00919556-e519-4960-8aa5-c2dfa020980c\",\n        \"0000173c-bf67-44e7-9a9c-1ad0bc728e74\",\n        \"00925f34-6baf-47fc-b40c-22591e27fb5c\",\n    },\n}\n\nMAPPING_DUPLICATED = {\n    \"well-2.pod5\": {\n        \"002fde30-9e23-4125-9eae-d112c18a81a7\",\n        \"002fde30-9e23-4125-9eae-d112c18a81a7\",\n        \"002fde30-9e23-4125-9eae-d112c18a81a7\",\n        \"00925f34-6baf-47fc-b40c-22591e27fb5c\",\n    },\n    \"well-4.pod5\": {\n        \"00919556-e519-4960-8aa5-c2dfa020980c\",\n        \"0000173c-bf67-44e7-9a9c-1ad0bc728e74\",\n        \"00925f34-6baf-47fc-b40c-22591e27fb5c\",\n        \"00925f34-6baf-47fc-b40c-22591e27fb5c\",\n    },\n}\n\nTEST_DATA_PATH = Path(__file__).parent.parent.parent.parent.parent / \"test_data\"\nPOD5_PATH = TEST_DATA_PATH / \"multi_fast5_zip_v4.pod5\"\n\n\nclass TestSubset:\n    \"\"\"Test that pod5 subset subsets files\"\"\"\n\n    @staticmethod\n    def csv_mapping_single(path: Path, mapping: Dict[str, Set[str]]) -> Path:\n        \"\"\"read ids are exploded over many lines\"\"\"\n        output = path / \"csv.csv\"\n        with output.open(\"w\") as _fh:\n            for target, read_ids in mapping.items():\n                for read_id in read_ids:\n                    _fh.write(f\"{path / target},{read_id}\\n\")\n        return output\n\n    def _test_subset(self, tmp: Path, csv: Path, mapping: Dict[str, Set[str]]) -> None:\n        # Known good mapping\n\n        targets = parse_csv_mapping(csv)\n        targets_dict = build_targets_dict(targets)\n\n        p5b.subset_pod5s_with_mapping(\n            [POD5_PATH],\n            tmp,\n            targets_dict,\n            # threads=threads,\n            False,\n            False,\n            False,\n        )\n\n        # Assert only the expected files are output\n        expected_outnames = list(mapping.keys())\n        actual_outnames = list(path.name for path in tmp.glob(\"*.pod5\"))\n        assert sorted(expected_outnames) == sorted(actual_outnames)\n\n        # Check all read_ids are present in their respective files\n        for outname in expected_outnames:\n            with pod5.Reader(tmp / outname) as reader:\n                assert reader.read_ids\n                # Set here asserts that there are no duplicates in putput\n                assert sorted(reader.read_ids) == sorted(set(mapping[outname]))\n\n    def test_subset_base(self, tmp_path: Path):\n        \"\"\"Test a known-good basic use case\"\"\"\n        csv = self.csv_mapping_single(tmp_path, MAPPING)\n        self._test_subset(tmp_path, csv, MAPPING)\n\n    def test_subset_shared_read_id(self, tmp_path: Path):\n        \"\"\"Test subsample with a mapping with shared reads_ids in outputs\"\"\"\n        csv = self.csv_mapping_single(tmp_path, MAPPING_REPEATED)\n        self._test_subset(tmp_path, csv, MAPPING_REPEATED)\n\n    def test_subset_duplicate_read_id(self, tmp_path: Path):\n        \"\"\"Test subsample with a mapping with shared reads_ids in outputs\"\"\"\n        csv = self.csv_mapping_single(tmp_path, MAPPING_REPEATED)\n        self._test_subset(tmp_path, csv, MAPPING_REPEATED)\n\n    def test_subset_dir_and_recurse(\n        self, tmp_path: Path, capsys: pytest.CaptureFixture\n    ) -> None:\n        csv = tmp_path / \"csv.csv\"\n        data = tmp_path / \"subdir/test.pod5\"\n        data.parent.mkdir(exist_ok=False, parents=True)\n        data.write_bytes(POD5_PATH.read_bytes())\n\n        inspect_pod5(\"reads\", [POD5_PATH])\n        captured_stdout = str(capsys.readouterr().out)\n        with csv.open(\"w\") as csv_write:\n            csv_write.writelines(captured_stdout)\n\n        output = tmp_path / \"output_dir\"\n        subset_pod5(\n            [tmp_path],\n            output=output,\n            csv=None,\n            table=csv,\n            columns=[\"well\"],\n            threads=2,\n            template=\"\",\n            read_id_column=\"read_id\",\n            missing_ok=False,\n            ignore_incomplete_template=False,\n            force_overwrite=False,\n            recursive=True,\n        )\n        assert output.exists()\n        for output_pod5 in output.glob(\"*.pod5\"):\n            with pod5.Reader(output_pod5) as reader:\n                assert reader.read_ids\n\n    def test_assert_overwrite(self, tmp_path: Path) -> None:\n        \"\"\"Test overwriting existing files if requested\"\"\"\n\n        # Create a file we know will be written to in `test_subset_base`:\n        (tmp_path / \"well-2.pod5\").touch()\n\n        # Run the test, expecting an error\n        with pytest.raises(RuntimeError):\n            self.test_subset_base(tmp_path)\n\n    def _pod5s_with_view_table(\n        self, num_input_pod5s: int, tmp_path: Path, pod5_factory\n    ) -> tuple[Path, Path]:\n        \"\"\"Create cached pod5s and the view table\"\"\"\n        pod5_path = Path.cwd()\n        num_reads = 0\n        print(f\"Creating {num_input_pod5s} pod5s for tests\")\n        for n in range(1, num_input_pod5s):\n            num_reads += n\n            pod5_path = Path(pod5_factory(n))\n        print(f\"Finished creating {num_input_pod5s} pod5s for tests\")\n        # Run pod5 view to create the view table\n        from pod5.tools.pod5_view import view_pod5\n\n        table = tmp_path / \"view.table\"\n        view_pod5(\n            inputs=[pod5_path.parent],\n            output=table,\n            recursive=True,\n        )\n\n        return pod5_path.parent, table\n\n    @pytest.mark.skipif(not HAS_RLIMIT, reason=\"POSIX only\")\n    def test_subset_ulimit_below_num_outputs(\n        self, tmp_path: Path, capsys: pytest.CaptureFixture, pod5_factory\n    ):\n        resource = get_resource_module()\n        assert resource is not None\n\n        num_input_pod5s = 50\n        pod5_dir, table = self._pod5s_with_view_table(\n            num_input_pod5s, tmp_path, pod5_factory\n        )\n        num_reads = sum(range(num_input_pod5s + 1))  # T(50)=1275\n\n        output = tmp_path / \"output_dir\"\n        cmd = [\n            sys.executable,\n            \"-m\",\n            \"pod5.tools.main\",\n            \"subset\",\n            \"--table\",\n            str(table),\n            \"--columns\",\n            \"channel\",  # assuming channel * mux is mostly unique\n            \"mux\",\n            \"--output\",\n            str(output),\n            \"--recursive\",\n            \"--force-overwrite\",\n            str(pod5_dir),\n        ]\n\n        def set_nofile_le_num_outputs():\n            # Set ulimit below max number of output handles.\n            # Using (num_reads / 2) as channel * mux should be unique per read\n            soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)\n            rlimit_soft, rlimit_hard = min(num_reads // 3, soft), hard\n            resource.setrlimit(resource.RLIMIT_NOFILE, (rlimit_soft, rlimit_hard))\n\n        r = subprocess.run(\n            cmd,\n            preexec_fn=set_nofile_le_num_outputs,\n            capture_output=True,\n            text=True,\n            check=False,\n        )\n\n        assert r.returncode == 0, r.stderr\n\n    @pytest.mark.skipif(not HAS_RLIMIT, reason=\"POSIX only\")\n    def test_subset_ulimit_below_num_inputs(\n        self, tmp_path: Path, capsys: pytest.CaptureFixture, pod5_factory\n    ):\n        resource = get_resource_module()\n        assert resource is not None\n\n        num_input_pod5s = 50\n        pod5_dir, table = self._pod5s_with_view_table(\n            num_input_pod5s, tmp_path, pod5_factory\n        )\n\n        output = tmp_path / \"output_dir\"\n        cmd = [\n            sys.executable,\n            \"-m\",\n            \"pod5.tools.main\",\n            \"subset\",\n            \"--table\",\n            str(table),\n            \"--columns\",\n            \"mux\",\n            \"--output\",\n            str(output),\n            \"--recursive\",\n            \"--force-overwrite\",\n            str(pod5_dir),\n        ]\n\n        def set_nofile_le_num_inputs():\n            # Set ulimit below max number of input handles.\n            soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)\n            rlimit_soft, rlimit_hard = min(num_input_pod5s - 4, soft), hard\n            resource.setrlimit(resource.RLIMIT_NOFILE, (rlimit_soft, rlimit_hard))\n\n        r = subprocess.run(\n            cmd,\n            preexec_fn=set_nofile_le_num_inputs,\n            capture_output=True,\n            text=True,\n            check=False,\n        )\n\n        assert r.returncode == 0, r.stderr\n\n\nclass TestFilenameTemplating:\n    \"\"\"Test the output filename templating\"\"\"\n\n    @pytest.mark.parametrize(\n        \"columns,expected\",\n        [\n            ([\"mux\"], \"mux-{mux}.pod5\"),\n            ([\"mux\", \"channel\"], \"mux-{mux}_channel-{channel}.pod5\"),\n            ([\"channel\", \"mux\"], \"channel-{channel}_mux-{mux}.pod5\"),\n        ],\n    )\n    def test_default_template(self, columns: List[str], expected: str):\n        template = create_default_filename_template(columns)\n        assert template == expected\n\n    @pytest.mark.parametrize(\n        \"keys,template\",\n        [\n            ([\"mux\"], \"mux-{mux}.pod5\"),\n            ([\"mux\", \"channel\"], \"mux-{mux}_channel-{channel}.pod5\"),\n            ([\"channel\", \"mux\"], \"channel-{channel}_mux-{mux}.pod5\"),\n            ([\"aa\", \"bb\", \"aa\"], \"{aa}_{bb}_{aa}.pod5\"),\n            ([\"cc\", \"cc\", \"cc\"], \"!{cc}.{cc}.{cc}\"),\n            ([\"cc\"], \"{{{cc}}}\"),\n            ([], \"{{{}}}\"),\n            ([], \"\"),\n            ([], \"foo.pod5\"),\n        ],\n    )\n    def test_column_keys_from_template(self, keys: List[str], template: str) -> None:\n        assert keys == column_keys_from_template(template)\n\n    @pytest.mark.parametrize(\n        \"keys,pl_template,template\",\n        [\n            ([\"mux\"], \"mux-{}.pod5\", \"mux-{mux}.pod5\"),\n            ([\"mux\", \"ch\"], \"mux-{}_ch-{}.pod5\", \"mux-{mux}_ch-{ch}.pod5\"),\n            ([\"ch\", \"mux\"], \"ch-{}_mux-{}.pod5\", \"ch-{ch}_mux-{mux}.pod5\"),\n            ([\"aa\", \"bb\", \"aa\"], \"{}_{}_{}.pod5\", \"{aa}_{bb}_{aa}.pod5\"),\n            ([\"cc\", \"cc\", \"cc\"], \"!{}.{}.{}\", \"!{cc}.{cc}.{cc}\"),\n            ([\"cc\"], \"{{{}}}\", \"{{{cc}}}\"),\n            ([], \"{{{}}}\", \"{{{}}}\"),\n            ([], \"\", \"\"),\n            ([], \"foo.pod5\", \"foo.pod5\"),\n        ],\n    )\n    def test_fstring_to_polars(\n        self, keys: List[str], pl_template: str, template: str\n    ) -> None:\n        expected_pl, expected_keys = fstring_to_polars(template)\n        assert expected_pl == pl_template\n        assert expected_keys == keys\n\n    def test_template_assertions(self) -> None:\n        with pytest.raises(KeyError):\n            assert_filename_template(\"some_{unknown}.pod5\", [\"known\"], True)\n\n        with pytest.raises(KeyError):\n            assert_filename_template(\"some_{unknown}_{known}.pod5\", [\"known\"], True)\n\n        # Ignore incomplete\n        assert_filename_template(\"some.pod5\", [\"known\"], True)\n        with pytest.raises(KeyError):\n            assert_filename_template(\"some.pod5\", [\"known\"], False)\n\n\nclass TestParse:\n    def test_csv_separator(self, tmp_path: Path) -> None:\n        csv = tmp_path / \"csv.csv\"\n        with csv.open(\"w\") as writer:\n            writer.writelines([\"this,is,a,csv,line\", \"some,other,line\"])\n        assert \",\" == get_separator(csv)\n\n    def test_tsv_separator(self, tmp_path: Path) -> None:\n        tsv = tmp_path / \"tsv.tsv\"\n        with tsv.open(\"w\") as writer:\n            writer.writelines([\"this\\tis\\ta\\ttab\\tline\", \"some\\tother\\tline\"])\n        assert \"\\t\" == get_separator(tsv)\n\n    def _inspect_reads_content(\n        self, paths: List[Path], capsys: pytest.CaptureFixture\n    ) -> str:\n        inspect_pod5(\"reads\", paths)\n        return str(capsys.readouterr().out)\n\n    def _write_csv(self, tmp_path: Path, content: str) -> Path:\n        csv_path = tmp_path / \"table.csv\"\n        with csv_path.open(\"w\") as csv:\n            csv.writelines(content.splitlines(keepends=True))\n        return csv_path\n\n    def _write_tsv(self, tmp_path: Path, content: str) -> Path:\n        tsv_path = tmp_path / \"table.tsv\"\n        with tsv_path.open(\"w\") as tsv:\n            tsv_content = content.replace(\",\", \"\\t\")\n            tsv.writelines(tsv_content.splitlines(keepends=True))\n        return tsv_path\n\n    def test_csv_tsv_parse_equal_1(\n        self, tmp_path: Path, capsys: pytest.CaptureFixture\n    ) -> None:\n        content = self._inspect_reads_content([POD5_PATH], capsys)\n        csv = self._write_csv(tmp_path=tmp_path, content=content)\n        tsv = self._write_tsv(tmp_path=tmp_path, content=content)\n\n        csv_ldf = parse_table_mapping(csv, None, [\"channel\"])\n        tsv_ldf = parse_table_mapping(tsv, None, [\"channel\"])\n\n        assert isinstance(csv_ldf, pl.LazyFrame)\n        assert isinstance(tsv_ldf, pl.LazyFrame)\n\n        csv_channel = csv_ldf.collect()\n        tsv_channel = tsv_ldf.collect()\n\n        assert len(csv_channel) > 0\n        assert len(csv_channel) == len(tsv_channel)\n        assert all(c == t for c, t in zip(csv_channel.rows(), tsv_channel.rows()))\n        assert_frame_equal(csv_channel, tsv_channel)\n\n        assert \"channel\" in csv_channel.columns\n        assert PL_READ_ID in csv_channel.columns\n        assert PL_DEST_FNAME in csv_channel.columns\n\n        expected_mapping = {\n            \"channel-109.pod5\": [\"0000173c-bf67-44e7-9a9c-1ad0bc728e74\"],\n            \"channel-126.pod5\": [\"007cc97e-6de2-4ff6-a0fd-1c1eca816425\"],\n            \"channel-147.pod5\": [\"00728efb-2120-4224-87d8-580fbb0bd4b2\"],\n            \"channel-199.pod5\": [\"00919556-e519-4960-8aa5-c2dfa020980c\"],\n            \"channel-2.pod5\": [\"008468c3-e477-46c4-a6e2-7d021a4ebf0b\"],\n            \"channel-452.pod5\": [\"009dc9bd-c5f4-487b-ba4c-b9ce7e3a711e\"],\n            \"channel-463.pod5\": [\"002fde30-9e23-4125-9eae-d112c18a81a7\"],\n            \"channel-474.pod5\": [\"008ed3dc-86c2-452f-b107-6877a473d177\"],\n            \"channel-489.pod5\": [\"006d1319-2877-4b34-85df-34de7250a47b\"],\n            \"channel-53.pod5\": [\"00925f34-6baf-47fc-b40c-22591e27fb5c\"],\n        }\n\n        records = []\n        for fname, rids in expected_mapping.items():\n            records.append([fname, rids])\n\n        expected = (\n            pl.from_records(records, schema=[PL_DEST_FNAME, PL_READ_ID], orient=\"row\")\n            .explode(PL_READ_ID)\n            .with_columns(pl.col(PL_DEST_FNAME).cast(pl.Categorical))\n        )\n\n        assert_series_equal(\n            expected.get_column(PL_DEST_FNAME).sort(),\n            csv_channel.get_column(PL_DEST_FNAME).sort(),\n        )\n        assert_series_equal(\n            expected.get_column(PL_READ_ID).sort(),\n            csv_channel.get_column(PL_READ_ID).sort(),\n        )\n\n    def test_csv_tsv_parse_equal_2(\n        self, tmp_path: Path, capsys: pytest.CaptureFixture\n    ) -> None:\n        content = self._inspect_reads_content([POD5_PATH], capsys)\n        csv = self._write_csv(tmp_path=tmp_path, content=content)\n        tsv = self._write_tsv(tmp_path=tmp_path, content=content)\n\n        csv_df = parse_table_mapping(csv, None, [\"well\", \"end_reason\"]).collect()\n        tsv_df = parse_table_mapping(tsv, None, [\"well\", \"end_reason\"]).collect()\n\n        assert len(csv_df) > 0\n        assert len(csv_df) == len(tsv_df)\n        assert all(c == t for c, t in zip(csv_df.rows(), tsv_df.rows()))\n        assert_frame_equal(csv_df, tsv_df)\n\n        assert \"well\" in csv_df.columns\n        assert \"end_reason\" in csv_df.columns\n\n        expected_mapping = {\n            \"well-2_end_reason-unknown.pod5\": [\n                \"002fde30-9e23-4125-9eae-d112c18a81a7\",\n                \"009dc9bd-c5f4-487b-ba4c-b9ce7e3a711e\",\n                \"008468c3-e477-46c4-a6e2-7d021a4ebf0b\",\n                \"00728efb-2120-4224-87d8-580fbb0bd4b2\",\n                \"007cc97e-6de2-4ff6-a0fd-1c1eca816425\",\n            ],\n            \"well-4_end_reason-unknown.pod5\": [\n                \"00919556-e519-4960-8aa5-c2dfa020980c\",\n                \"0000173c-bf67-44e7-9a9c-1ad0bc728e74\",\n                \"008ed3dc-86c2-452f-b107-6877a473d177\",\n                \"006d1319-2877-4b34-85df-34de7250a47b\",\n                \"00925f34-6baf-47fc-b40c-22591e27fb5c\",\n            ],\n        }\n\n        records = []\n        for fname, rids in expected_mapping.items():\n            records.append([fname, rids])\n\n        expected = (\n            pl.from_records(records, schema=[PL_DEST_FNAME, PL_READ_ID], orient=\"row\")\n            .explode(PL_READ_ID)\n            .with_columns(pl.col(PL_DEST_FNAME).cast(pl.Categorical))\n        )\n\n        assert_series_equal(\n            expected.get_column(PL_DEST_FNAME).sort(),\n            csv_df.get_column(PL_DEST_FNAME).sort(),\n        )\n        assert_series_equal(\n            expected.get_column(PL_READ_ID).sort(),\n            csv_df.get_column(PL_READ_ID).sort(),\n        )\n\n    def test_parse_csv_filters_invalid_read_ids(self, tmp_path: Path) -> None:\n        csv = tmp_path / \"invalid.csv\"\n        valid_id = \"00000000-0000-0000-0000-000000000000\"\n        invalid_id = \"not-a-uuid\"\n        with csv.open(\"w\") as writer:\n            writer.write(f\"{tmp_path / 'valid.pod5'},{valid_id}\\n\")\n            writer.write(f\"{tmp_path / 'invalid.pod5'},{invalid_id}\\n\")\n\n        parsed = parse_csv_mapping(csv).collect()\n\n        assert parsed.height == 1\n        assert parsed.get_column(PL_READ_ID).to_list() == [valid_id]\n        assert parsed.get_column(PL_DEST_FNAME).to_list() == [\n            str(tmp_path / \"valid.pod5\")\n        ]\n\n    def test_parse_table_filters_invalid_read_ids(self, tmp_path: Path) -> None:\n        table = tmp_path / \"table.csv\"\n        valid_id = \"11111111-1111-1111-1111-111111111111\"\n        invalid_id = \"1234\"\n        with table.open(\"w\") as writer:\n            writer.writelines(\n                [\n                    \"sample,read_id\\n\",\n                    f\"A,{valid_id}\\n\",\n                    f\"B,{invalid_id}\\n\",\n                ]\n            )\n\n        parsed = parse_table_mapping(table, None, [\"sample\"]).collect()\n\n        assert parsed.height == 1\n        assert parsed.get_column(PL_READ_ID).to_list() == [valid_id]\n        assert parsed.get_column(PL_DEST_FNAME).to_list() == [\"sample-A.pod5\"]\n"
  },
  {
    "path": "python/pod5/src/tests/test_tools.py",
    "content": "\"\"\"\nTesting Pod5 Tools\n\"\"\"\n\nimport argparse\nimport os\nfrom pathlib import Path\nimport subprocess\nimport sys\nfrom typing import Callable, Dict\nfrom unittest.mock import Mock, patch\nfrom uuid import UUID\n\nimport h5py\nimport numpy as np\nfrom pod5.tools.utils import collect_inputs, limit_threads\nimport pytest\nimport vbz_h5py_plugin  # noqa: F401\n\nimport pod5\nfrom pod5.tools import main, parsers\n\nTEST_DATA_PATH = Path(__file__).parent.parent.parent.parent.parent / \"test_data\"\nFAST5_PATH = TEST_DATA_PATH / \"multi_fast5_zip.fast5\"\nPOD5_PATH = TEST_DATA_PATH / \"multi_fast5_zip_v4.pod5\"\nSUBSET_CSV_PATH = TEST_DATA_PATH / \"subset_mapping_examples/subset.csv\"\nREAD_IDS_PATH = TEST_DATA_PATH / \"subset_mapping_examples/read_ids.txt\"\n\n\ndef assert_exit_code(func: Callable, func_kwargs: Dict, exit_code: int = 0) -> None:\n    \"\"\"Assert that a function returns the given SystemExit exit code\"\"\"\n    try:\n        func(**func_kwargs)\n    except SystemExit as exc:\n        assert exc.code == exit_code\n\n\nclass TestPod5Tools:\n    \"\"\"Test the Pod5 tools interface\"\"\"\n\n    @patch(\"pod5.tools.main.run_tool\")\n    def test_main_calls_run(self, m_run_tool: Mock) -> None:\n        \"\"\"Assert that main calls run_tool and that it returns to main\"\"\"\n        m_run_tool.return_value = \"_return_value\"\n        return_value = main.main()\n        m_run_tool.assert_called()\n        assert return_value == \"_return_value\"\n\n    def test_run_tool_debug_env(\n        self, capsys: pytest.CaptureFixture, monkeypatch: pytest.MonkeyPatch\n    ) -> None:\n        \"\"\"Assert that exceptions are printed nicely without POD5_DEBUG\"\"\"\n\n        dummy_error_string = \"Dummy Error String\"\n\n        def _func() -> None:\n            raise Exception(dummy_error_string)\n\n        parser = argparse.ArgumentParser()\n        parser.set_defaults(func=_func)\n\n        # Intentionally raise an error\n        with monkeypatch.context() as mkp:\n            mkp.setenv(\"POD5_DEBUG\", \"0\")\n            mkp.setattr(\"argparse._sys.argv\", [\"_raises_an_exception\"])\n            assert_exit_code(parsers.run_tool, {\"parser\": parser}, 1)\n\n        error_str: str = capsys.readouterr().err\n        assert \"POD5_DEBUG=1\" in error_str\n        assert dummy_error_string in error_str\n\n    def test_run_tool_raises(self, monkeypatch: pytest.MonkeyPatch) -> None:\n        \"\"\"Assert that exceptions are raised if POD5_DEBUG is set\"\"\"\n        dummy_error_string = \"Dummy Error String\"\n\n        def _func() -> None:\n            raise Exception(dummy_error_string)\n\n        parser = argparse.ArgumentParser()\n        parser.set_defaults(func=_func)\n\n        # Intentionally raise an error\n        with monkeypatch.context() as mkp:\n            mkp.setenv(\"POD5_DEBUG\", \"1\")\n            mkp.setattr(\"argparse._sys.argv\", [\"_raises_an_exception\"])\n\n            with pytest.raises(Exception, match=dummy_error_string):\n                parsers.run_tool(parser)\n\n    def test_pod5_version_argument(self, capsys: pytest.CaptureFixture) -> None:\n        \"\"\"Assert that pod5 has a --version argument\"\"\"\n        with patch(\"argparse._sys.argv\", [\"pod5\", \"--version\"]):\n            assert_exit_code(main.main, {}, 0)\n\n        assert f\"pod5 version: {pod5.__version__}\" in capsys.readouterr().out.lower()\n\n    @pytest.mark.parametrize(\"subcommand\", [\"fast5\", \"to_fast5\", \"from_fast5\"])\n    def test_convert_exists(self, subcommand: str) -> None:\n        \"\"\"Assert that pod5 convert exists\"\"\"\n\n        with patch(\"argparse._sys.argv\", [\"pod5\", \"convert\", subcommand, \"--help\"]):\n            assert_exit_code(main.main, {}, 0)\n\n    @pytest.mark.parametrize(\"subcommand\", [\"summary\", \"read\", \"reads\", \"debug\"])\n    def test_inspect_exists(self, subcommand: str) -> None:\n        \"\"\"Assert that pod5 inspect exists\"\"\"\n\n        with patch(\"argparse._sys.argv\", [\"pod5\", \"inspect\", subcommand, \"--help\"]):\n            assert_exit_code(main.main, {}, 0)\n\n    @pytest.mark.parametrize(\n        \"command\",\n        [\"convert\", \"inspect\", \"filter\", \"merge\", \"subset\", \"repack\", \"update\"],\n    )\n    def test_tool_exists(self, command: str) -> None:\n        \"\"\"Assert that a pod5 tool exists\"\"\"\n\n        with patch(\"argparse._sys.argv\", [\"pod5\", command, \"--help\"]):\n            assert_exit_code(main.main, {}, 0)\n\n    def test_convert_from_fast5_runs(self, tmp_path: Path) -> None:\n        \"\"\"Assert that typical commands are valid\"\"\"\n\n        args = [\n            \"pod5\",\n            \"convert\",\n            \"from_fast5\",\n            str(FAST5_PATH),\n            \"--output\",\n            str(tmp_path / \"new.pod5\"),\n            \"--strict\",\n        ]\n        with patch(\"argparse._sys.argv\", args):\n            main.main()\n\n    def test_convert_to_fast5_runs(self, tmp_path: Path) -> None:\n        \"\"\"Assert that typical commands are valid\"\"\"\n\n        outdir = tmp_path / \"outdir\"\n        args = [\n            \"pod5\",\n            \"convert\",\n            \"to_fast5\",\n            str(POD5_PATH),\n            \"--output\",\n            str(outdir),\n        ]\n        with patch(\"argparse._sys.argv\", args):\n            main.main()\n\n        assert outdir.exists()\n        fast5s = list(outdir.glob(\"*.fast5\"))\n        assert len(fast5s) > 0\n\n        with h5py.File(fast5s[0]) as f5:\n            read_id = str(list(f5.keys())[0])\n            assert read_id.startswith(\"read_\")\n            assert UUID(read_id[len(\"read_\") :])\n            signal = np.array(f5[read_id][\"Raw/Signal\"])\n            assert len(signal) > 0\n\n    @pytest.mark.parametrize(\"subcommand\", [\"summary\", \"reads\"])\n    def test_inspect_command_runs(self, tmp_path: Path, subcommand: str) -> None:\n        \"\"\"Assert that typical commands are valid\"\"\"\n\n        args = [\n            \"pod5\",\n            \"inspect\",\n            subcommand,\n            str(POD5_PATH),\n        ]\n        with patch(\"argparse._sys.argv\", args):\n            main.main()\n\n    def test_inspect_read_finds_read(self, capsys: pytest.CaptureFixture) -> None:\n        \"\"\"Assert that inspect read finds a known read\"\"\"\n\n        args = [\n            \"pod5\",\n            \"inspect\",\n            \"read\",\n            str(POD5_PATH),\n            \"0000173c-bf67-44e7-9a9c-1ad0bc728e74\",\n        ]\n        with patch(\"argparse._sys.argv\", args):\n            main.main()\n\n        stdout_lines = str(capsys.readouterr().out).splitlines()\n\n        # A few expected lines from the tool\n        assert \"read_id: 0000173c-bf67-44e7-9a9c-1ad0bc728e74\" in stdout_lines\n        assert \"read_number:\\t1093\" in stdout_lines\n        assert \"start_sample:\\t4534321\" in stdout_lines\n        assert \"median_before:\\t183.1077423095703\" in stdout_lines\n\n    def test_merge_command_runs(self, tmp_path: Path) -> None:\n        \"\"\"Assert that typical commands are valid\"\"\"\n\n        args = [\n            \"pod5\",\n            \"merge\",\n            str(POD5_PATH),\n            \"--output\",\n            str(tmp_path / \"new.pod5\"),\n        ]\n        with patch(\"argparse._sys.argv\", args):\n            main.main()\n\n    def test_repack_command_runs(self, tmp_path: Path) -> None:\n        \"\"\"Assert that typical commands are valid\"\"\"\n\n        args = [\n            \"pod5\",\n            \"repack\",\n            str(POD5_PATH),\n            \"--output\",\n            str(tmp_path / \"new.pod5\"),\n        ]\n        with patch(\"argparse._sys.argv\", args):\n            main.main()\n\n    def test_recover_command_runs(self) -> None:\n        \"\"\"Assert that typical commands are valid\"\"\"\n\n        args = [\n            \"pod5\",\n            \"recover\",\n            str(POD5_PATH),\n        ]\n        with patch(\"argparse._sys.argv\", args):\n            main.main()\n\n    def test_subset_command_runs(self, tmp_path: Path) -> None:\n        \"\"\"Assert that typical commands are valid\"\"\"\n\n        output = Path(tmp_path / \"test_dir\")\n        output.mkdir()\n        args = [\n            \"pod5\",\n            \"subset\",\n            str(POD5_PATH),\n            \"--output\",\n            str(output),\n            \"--csv\",\n            str(SUBSET_CSV_PATH),\n        ]\n        with patch(\"argparse._sys.argv\", args):\n            main.main()\n\n        # assert len(list(output.rglob(\"*pod5\"))) == 2\n\n    def test_filter_command_runs(self, tmp_path: Path) -> None:\n        \"\"\"Assert that typical commands are valid\"\"\"\n\n        args = [\n            \"pod5\",\n            \"filter\",\n            str(POD5_PATH),\n            \"--output\",\n            str(tmp_path / \"take.pod5\"),\n            \"--ids\",\n            str(READ_IDS_PATH),\n        ]\n        with patch(\"argparse._sys.argv\", args):\n            main.main()\n\n    def test_update_command_runs(self, tmp_path: Path) -> None:\n        \"\"\"Assert that typical commands are valid\"\"\"\n\n        args = [\n            \"pod5\",\n            \"update\",\n            str(POD5_PATH),\n            \"--output\",\n            str(tmp_path),\n        ]\n        with patch(\"argparse._sys.argv\", args):\n            main.main()\n\n    def test_view_command_runs(self) -> None:\n        \"\"\"Assert that typical commands are valid\"\"\"\n\n        args = [\n            \"pod5\",\n            \"view\",\n            str(POD5_PATH),\n        ]\n        with patch(\"argparse._sys.argv\", args):\n            main.main()\n\n    def test_view_command_runs_list_fields(self) -> None:\n        \"\"\"Assert that typical commands are valid\"\"\"\n\n        args = [\n            \"pod5\",\n            \"view\",\n            \"--list-fields\",\n        ]\n        with patch(\"argparse._sys.argv\", args):\n            main.main()\n\n    @pytest.mark.skipif(\n        sys.platform.startswith(\"win\") and sys.version_info < (3, 8),\n        reason=\"windows py3.7 pathlib concatenation issue\",\n    )\n    @pytest.mark.parametrize(\n        \"script\", list((Path(__file__).parent.parent / \"pod5/tools\").glob(\"pod5*.py\"))\n    )\n    def test_scripts_run_directly(self, script: Path) -> None:\n        \"\"\"pod5 tools should run if executed directly as scripts\"\"\"\n        python_exe = Path(sys.executable)\n        subprocess.check_call([python_exe, script.absolute(), \"--help\"])\n\n\nclass TestUtils:\n    def test_collect_inputs(self, tmp_path: Path) -> None:\n        expected = [\n            tmp_path / \"a.pod5\",\n            tmp_path / \"longer-name.pod5\",\n            tmp_path / \"sub/a.pod5\",\n            tmp_path / \"sub/sub2/xx.pod5\",\n        ]\n        not_expected = [\n            tmp_path / \".pod5\",  # Exclude hidden files\n            tmp_path / \"other.txt\",\n            tmp_path / \"pod5.p5\",\n            tmp_path / \"a.pod5.p5\",\n            tmp_path / \"sub/other.png\",\n            tmp_path / \"sub/sub3/bad.pods\",\n        ]\n\n        for path in expected + not_expected:\n            path.parent.mkdir(parents=True, exist_ok=True)\n            path.touch()\n\n        assert all([path.exists() for path in expected + not_expected])\n\n        # Expect all pod5s recursively\n        recurse = collect_inputs([tmp_path], recursive=True, pattern=\"*.pod5\")\n        assert set(expected) == set(tmp_path.rglob(\"*[a-z0-9].pod5\"))\n        assert recurse == set(expected)\n        assert set(not_expected).isdisjoint(recurse)\n\n        # Expect all pod5s in top level\n        top = collect_inputs([tmp_path], recursive=False, pattern=\"*.pod5\")\n        assert set(tmp_path.glob(\"*[a-z0-9].pod5\")) == top\n        assert set(top).isdisjoint(not_expected)\n\n        # Files aren't duplicated in similar patterns\n        dupl = collect_inputs([tmp_path], recursive=False, pattern=[\"*.pod5\", \"*d5\"])\n        assert dupl == top\n\n        # Expect no matches\n        assert not collect_inputs([tmp_path], recursive=True, pattern=\"*.none\")\n\n        # Expect file_pattern to find other than *.pod5\n        p5_suffix = collect_inputs([tmp_path], recursive=True, pattern=\"*.p5\")\n        assert p5_suffix\n        assert p5_suffix == set(path for path in not_expected if path.suffix == \".p5\")\n\n        expect_mixed = set([tmp_path / \"other.txt\", tmp_path / \"sub/other.png\"])\n        assert expect_mixed == collect_inputs(\n            [tmp_path], recursive=True, pattern=[\"*.txt\", \"*.png\"]\n        )\n\n    def test_collect_inputs_non_existent(self, tmp_path: Path) -> None:\n        \"\"\"Test FileExistsError raised if input doesn't exist\"\"\"\n        with pytest.raises(FileExistsError, match=\"inputs do not exist\"):\n            collect_inputs([tmp_path / \"non_existent.txt\"], False, \"*.txt\")\n\n    @pytest.mark.skipif(os.cpu_count() is None, reason=\"os.cpu_count is None\")\n    def test_limit_threads(self) -> None:\n        \"\"\"Test thread limiting\"\"\"\n        cpus = os.cpu_count()\n        if cpus is None:\n            assert False\n        limit_threads(-1) == cpus\n        limit_threads(0) == cpus\n        for i in range(1, cpus + 1):\n            assert limit_threads(i) == i\n        assert limit_threads(cpus + 1) == cpus\n        limit_threads(1_000_000) == cpus\n"
  },
  {
    "path": "python/pod5/src/tests/test_update.py",
    "content": "from pathlib import Path\nimport packaging.version\nimport pod5 as p5\nfrom pod5.tools.pod5_update import update_pod5\nimport pytest\n\n\nTEST_DATA_PATH = Path(__file__).parent.parent.parent.parent.parent / \"test_data\"\nPOD5_V1_PATH = TEST_DATA_PATH / \"multi_fast5_zip_v1.pod5\"\nPOD5_V2_PATH = TEST_DATA_PATH / \"multi_fast5_zip_v2.pod5\"\nPOD5_PATH = TEST_DATA_PATH / \"multi_fast5_zip_v4.pod5\"\n\n\nclass TestUpdate:\n    \"\"\"Test that pod5 update runs\"\"\"\n\n    def test_detect_inplace_update(self, tmp_path: Path) -> None:\n        \"\"\"detect input is output and raise AssertionError\"\"\"\n        example = tmp_path / \"my.pod5\"\n        example.touch()\n\n        with pytest.raises(AssertionError, match=\"in-place\"):\n            update_pod5([tmp_path], tmp_path, force_overwrite=False, recursive=True)\n\n    def test_update(self, tmp_path: Path) -> None:\n        \"\"\"\n        Test update updates files and doesn't overwrite existing files unless forced\n        \"\"\"\n        inputs = tmp_path / \"data\"\n        inputs.mkdir(parents=True)\n        v1 = inputs / \"v1.pod5\"\n        v1.write_bytes(POD5_V2_PATH.read_bytes())\n\n        with p5.Reader(v1) as reader:\n            assert reader.file_version_pre_migration < packaging.version.Version(\"0.1\")\n\n        exists = tmp_path / \"v1.pod5\"\n        exists.touch()\n\n        # Test no overwrite\n        with pytest.raises(FileExistsError, match=\"--force-overwrite\"):\n            update_pod5(\n                [inputs],\n                tmp_path,\n                force_overwrite=False,\n                recursive=True,\n            )\n\n        update_pod5(\n            [inputs],\n            tmp_path,\n            force_overwrite=True,\n            recursive=True,\n        )\n        assert exists.is_file()\n        with p5.Reader(exists) as reader:\n            assert reader.file_version_pre_migration > packaging.version.Version(\"0.1\")\n            assert reader.read_ids\n"
  },
  {
    "path": "python/pod5/src/tests/test_view.py",
    "content": "from pathlib import Path\nimport random\nfrom typing import Any, Dict\nimport polars as pl\n\nimport pytest\n\nimport pod5 as p5\nfrom pod5.tools.pod5_view import (\n    Field,\n    assert_unique_acquisition_id,\n    get_included_reads_table_fields,\n    get_reads_tables,\n    join_reads_to_run_info,\n    parse_read_table_chunks,\n    parse_reads_table_all,\n    parse_run_info_table,\n    view_pod5,\n    select_fields,\n    get_field_or_raise,\n    resolve_output,\n    write,\n    write_header,\n    FIELDS,\n)\n\n\nTEST_DATA_PATH = Path(__file__).parent.parent.parent.parent.parent / \"test_data\"\nPOD5_PATH = TEST_DATA_PATH / \"multi_fast5_zip_v4.pod5\"\n\nALL_FIELDS = [\n    \"read_id\",\n    \"filename\",\n    \"read_number\",\n    \"channel\",\n    \"mux\",\n    \"end_reason\",\n    \"start_time\",\n    \"start_sample\",\n    \"duration\",\n    \"num_samples\",\n    \"minknow_events\",\n    \"sample_rate\",\n    \"median_before\",\n    \"predicted_scaling_scale\",\n    \"predicted_scaling_shift\",\n    \"tracked_scaling_scale\",\n    \"tracked_scaling_shift\",\n    \"num_reads_since_mux_change\",\n    \"time_since_mux_change\",\n    \"run_id\",\n    \"sample_id\",\n    \"experiment_id\",\n    \"flow_cell_id\",\n    \"pore_type\",\n    \"open_pore_level\",\n]\n\n\nclass TestView:\n    \"\"\"Test view application\"\"\"\n\n    def is_equal_or_not_set(self, field: str, expected: str) -> None:\n        assert field == expected or (field == \"\" and expected == \"not_set\")\n\n    def _compare(self, record: p5.ReadRecord, row: Dict[str, Any]) -> None:\n        assert str(record.read_id) == row[\"read_id\"]\n        assert record.read_number == int(row[\"read_number\"])\n        assert record.pore.well == int(row[\"mux\"])\n        assert record.pore.channel == int(row[\"channel\"])\n        assert record.end_reason.name == row[\"end_reason\"]\n        assert record.start_sample / record.run_info.sample_rate == float(\n            row[\"start_time\"]\n        )\n        assert record.start_sample == int(row[\"start_sample\"])\n        assert record.num_samples / record.run_info.sample_rate == float(\n            row[\"duration\"]\n        )\n        assert record.num_samples == int(row[\"num_samples\"])\n        assert record.num_minknow_events == float(row[\"minknow_events\"])\n        assert record.run_info.sample_rate == int(row[\"sample_rate\"])\n        pytest.approx(record.median_before, float(row[\"median_before\"]))\n        pytest.approx(\n            record.predicted_scaling.scale, float(row[\"predicted_scaling_scale\"])\n        )\n        pytest.approx(\n            record.predicted_scaling.shift, float(row[\"predicted_scaling_shift\"])\n        )\n        pytest.approx(record.tracked_scaling.scale, float(row[\"tracked_scaling_scale\"]))\n        pytest.approx(record.tracked_scaling.shift, float(row[\"tracked_scaling_shift\"]))\n        assert record.num_reads_since_mux_change == int(\n            row[\"num_reads_since_mux_change\"]\n        )\n        pytest.approx(record.time_since_mux_change, float(row[\"time_since_mux_change\"]))\n        pytest.approx(record.open_pore_level, float(row[\"open_pore_level\"]))\n        assert record.run_info.protocol_run_id == row[\"run_id\"]\n        self.is_equal_or_not_set(record.run_info.sample_id, row[\"sample_id\"])\n        self.is_equal_or_not_set(record.run_info.experiment_name, row[\"experiment_id\"])\n        self.is_equal_or_not_set(record.run_info.flow_cell_id, row[\"flow_cell_id\"])\n        assert record.pore.pore_type == row[\"pore_type\"]\n\n    def test_view(self, tmp_path: Path):\n        \"\"\"Test that the merge tool runs a trivial example\"\"\"\n\n        # Test all pod5 inputs in test data, which will likely contain duplicates\n        output = tmp_path / \"test.tsv\"\n        view_pod5([POD5_PATH], output)\n\n        assert output.exists()\n\n        with output.open(\"r\") as _fh:\n            content = _fh.readlines()\n\n        # 10 lines + 1 header\n        assert len(content) == 11\n\n        header = content[0]\n        assert list(map(str.strip, header.split(\"\\t\"))) == ALL_FIELDS\n\n        with p5.Reader(POD5_PATH) as reader:\n            for idx, record in enumerate(reader):\n                items = list(map(str.strip, content[idx + 1].split(\"\\t\")))\n                row = {name: items[ALL_FIELDS.index(name)] for name in ALL_FIELDS}\n                POD5_PATH.name == row[\"filename\"]\n\n                self._compare(record, row)\n\n            assert idx == 9\n\n    def test_view_no_input(self, tmp_path: Path):\n        \"\"\"Test that the merge tool raises AssertionError if found no files\"\"\"\n        with pytest.raises(AssertionError, match=\"Found no pod5 files\"):\n            view_pod5([tmp_path], tmp_path)\n\n    def test_write_stdout(self, capsys: pytest.CaptureFixture) -> None:\n        \"\"\"Test that polars writes to stdout when path is None\"\"\"\n\n        ldf = next(get_reads_tables(POD5_PATH, select_fields()))\n        write_header(None, select_fields())\n        write(ldf, None)\n        content: str = capsys.readouterr().out\n        err: str = capsys.readouterr().err\n        assert not err\n        lines = content.splitlines()\n        header = lines[0]\n        assert list(map(str.strip, header.split(\"\\t\"))) == ALL_FIELDS\n        # Empty trailing line\n        assert len(lines) == 11\n        assert len(set(lines)) == len(lines)\n\n    def test_is_loadable(self, tmp_path: Path) -> None:\n        output = tmp_path / \"test.tsv\"\n        view_pod5([POD5_PATH], output)\n\n        df = pl.read_csv(output, separator=\"\\t\")\n        with p5.Reader(POD5_PATH) as reader:\n            for idx, record in enumerate(reader):\n                row = df.row(idx, named=True)\n                POD5_PATH.name == row[\"filename\"]\n\n                self._compare(record, row)\n\n    def test_parse_run_info(self, pod5_factory) -> None:\n        \"\"\"Test the run_info table parser\"\"\"\n        selection = select_fields()\n        a_pod5 = pod5_factory(10)\n        with p5.Reader(a_pod5) as reader:\n            run_info = parse_run_info_table(reader, selection)\n\n        assert isinstance(run_info, pl.LazyFrame)\n        run_info = run_info.collect()\n        assert run_info.is_unique().all()\n\n        schema_names = run_info.collect_schema().names()\n        assert \"context_tags\" not in schema_names\n        assert \"tracking_id\" not in schema_names\n        for run_info_field in selection.info_fields:\n            assert run_info_field in schema_names\n\n    def test_parse_reads_all(self, pod5_factory) -> None:\n        \"\"\"Test the reads table parser where the file is small enough to do in one go\"\"\"\n        selection = select_fields()\n        a_pod5 = pod5_factory(10)\n        with p5.Reader(a_pod5) as reader:\n            included_fields = get_included_reads_table_fields(reader, selection)\n            reads = parse_reads_table_all(reader, included_fields)\n\n        assert isinstance(reads, pl.LazyFrame)\n\n        schema_names = reads.collect_schema().names()\n        assert \"read_id\" in schema_names\n        assert \"run_info\" in schema_names\n        for reads_field in selection.reads_fields:\n            reads_field in schema_names\n\n        assert len(reads.collect()) == 10\n\n    def test_parse_reads_multi_chunk(self, pod5_factory) -> None:\n        \"\"\"Test the reads table parser\"\"\"\n        selection = select_fields()\n        a_pod5 = pod5_factory(1100)\n        with p5.Reader(a_pod5) as reader:\n            included_fields = get_included_reads_table_fields(reader, selection)\n            tables = [\n                t\n                for t in parse_read_table_chunks(\n                    reader, included_fields, approx_size=999\n                )\n            ]\n\n        assert len(tables) == 2\n        for table in tables:\n            assert isinstance(table, pl.LazyFrame)\n            schema_names = table.collect_schema().names()\n            assert \"read_id\" in schema_names\n            assert \"run_info\" in schema_names\n            for reads_field in selection.reads_fields:\n                reads_field in schema_names\n\n        all_reads = pl.concat(tables)\n        assert len(all_reads.collect()) == 1100\n\n    def test_unique_on_duplicated_run_info(self) -> None:\n        \"\"\"Legacy bug where run_info data was duplicated\"\"\"\n        reads_data = {\"read_id\": [\"a\", \"b\", \"c\"], \"run_info\": [\"r1\", \"r1\", \"r1\"]}\n        reads = pl.DataFrame(reads_data).lazy()\n\n        run_info_data_dupl = {\"acquisition_id\": [\"r1\", \"r1\"], \"data\": [\"d1\", \"d1\"]}\n        run_info = pl.DataFrame(run_info_data_dupl).lazy()\n\n        assert_unique_acquisition_id(run_info, Path.cwd())\n        joined = join_reads_to_run_info(reads, run_info)\n\n        assert len(reads.collect()) == 3\n        assert len(run_info.collect()) == 2\n        # If len(joined) is 6, the uniqueness of run_info has failed and the\n        # join operation has doubled-up every row\n        assert len(joined.collect()) == 3\n\n\nclass TestSelection:\n    \"\"\"Test selection options\"\"\"\n\n    def test_select(self) -> None:\n        \"\"\"Test select options\"\"\"\n        assert set(ALL_FIELDS) == select_fields().selected\n\n        assert {\"read_id\"} == select_fields(group_read_id=True).selected\n\n        assert {\"read_id\"} == select_fields(include=\"read_id\").selected\n        assert {\"read_id\", \"filename\"} == select_fields(\n            include=\"read_id,filename\"\n        ).selected\n        assert {\"read_id\", \"filename\"} == select_fields(\n            include=\",read_id,filename,,,\"\n        ).selected\n        assert {\"read_id\", \"filename\"} == select_fields(\n            include=\" read_id ,  filename \"\n        ).selected\n        assert {\"mux\", \"channel\"} == select_fields(include=\" mux,  channel\").selected\n        assert set(ALL_FIELDS) == select_fields(include=\",\".join(ALL_FIELDS)).selected\n        assert set(ALL_FIELDS) == select_fields(include=\"\").selected\n\n        assert \"read_id\" not in select_fields(exclude=\"read_id\").selected\n        assert {\"pore_type\", \"mux\"} not in select_fields(\n            exclude=\"pore_type,mux\"\n        ).selected\n        assert set(ALL_FIELDS) == select_fields(exclude=\"\").selected\n        assert set(ALL_FIELDS) == select_fields(exclude=\", ,\").selected\n\n        drop_rid = set(ALL_FIELDS) - {\"read_id\"}\n        assert drop_rid == select_fields(exclude=\"read_id\").selected\n        assert drop_rid == select_fields(exclude=\",read_id,,   ,\").selected\n\n    @pytest.mark.parametrize(\"field\", [\"read_i\", \"mix\", \"ed_reason\", \"_\", \"channell\"])\n    def test_misspelling(self, field: str) -> None:\n        \"\"\"Test select raises errors on unknown / misspelled fields\"\"\"\n        with pytest.raises(KeyError, match=f\"Field: '{field}' did not match\"):\n            select_fields(include=field)\n\n    def test_randomly(self) -> None:\n        \"\"\"Randomly include / exclude\"\"\"\n        for idx in range(1_000):\n            random.seed(idx)\n            incl = set(random.choices(ALL_FIELDS, k=random.randint(1, len(ALL_FIELDS))))\n            excl = set(random.choices(ALL_FIELDS, k=random.randint(0, len(ALL_FIELDS))))\n\n            expected = incl - excl\n            include = \",\".join(incl)\n            exclude = \",\".join(excl)\n            try:\n                assert (\n                    expected == select_fields(include=include, exclude=exclude).selected\n                )\n            except RuntimeError:\n                assert len(expected) == 0\n\n    def test_get_field(self) -> None:\n        \"\"\"Test get_field_or_raise\"\"\"\n        with pytest.raises(KeyError, match=\"any known fields\"):\n            get_field_or_raise(\"blah\")\n\n        with pytest.raises(KeyError, match=\"any known fields\"):\n            get_field_or_raise(\"\")\n\n        for field in ALL_FIELDS:\n            ret = get_field_or_raise(field)\n            assert isinstance(ret, Field)\n\n\nclass TestMisc:\n    def test_resolve_output(self, tmp_path: Path) -> None:\n        assert resolve_output(None, True) is None\n        assert resolve_output(None, False) is None\n\n        no_exist = tmp_path / \"no_exist\"\n        assert resolve_output(no_exist, False) == no_exist\n        assert resolve_output(no_exist, True) == no_exist\n\n        exist = tmp_path / \"exist\"\n        exist.touch()\n        assert resolve_output(exist, True) == exist\n\n        exist.touch()\n        with pytest.raises(FileExistsError):\n            resolve_output(exist, False)\n\n        # Test the default output path if a directory is given\n        assert tmp_path / \"view.txt\" == resolve_output(tmp_path, False)\n\n    def test_fields(self) -> None:\n        assert all(key == field for key, field in zip(ALL_FIELDS, FIELDS.keys()))\n        assert len(FIELDS) > 0\n\n    def test_unique_acquisition_id(self) -> None:\n        pass_example = pl.DataFrame(\n            {\"acquisition_id\": [1, 1, 3], \"b\": [1, 1, 3], \"c\": [2, 2, 3]}\n        ).lazy()\n        assert_unique_acquisition_id(pass_example, Path(\"none\"))\n\n        fail_example = pl.DataFrame(\n            {\"acquisition_id\": [1, 1, 3], \"b\": [1, 2, 3], \"c\": [2, 1, 3]}\n        ).lazy()\n        with pytest.raises(AssertionError, match=\"acquisition_id\"):\n            assert_unique_acquisition_id(fail_example, Path(\"none\"))\n"
  },
  {
    "path": "python/pod5/src/tests/test_writer.py",
    "content": "\"\"\"\nTesting Pod5Writer\n\"\"\"\n\nimport math\nimport lib_pod5 as p5b\nimport numpy as np\nimport pytest\n\nimport pod5 as p5\n\n\nclass TestPod5Writer:\n    \"\"\"Test the Pod5Writer from a pod5 file\"\"\"\n\n    def test_writer_fixture(self, writer: p5.Writer) -> None:\n        \"\"\"Basic assertions on the writer fixture\"\"\"\n        assert isinstance(writer, p5.Writer)\n        assert isinstance(writer._writer, p5b.FileWriter)\n\n    @pytest.mark.parametrize(\"random_read\", [1, 2, 3, 4], indirect=True)\n    def test_writer_random_reads(self, writer: p5.Writer, random_read: p5.Read) -> None:\n        \"\"\"Write some random single reads to a writer\"\"\"\n\n        writer.add_read(random_read)\n\n    @pytest.mark.parametrize(\"random_read_pre_compressed\", [1], indirect=True)\n    def test_writer_random_reads_compressed(\n        self, writer: p5.Writer, random_read_pre_compressed: p5.Read\n    ) -> None:\n        \"\"\"Write some random single reads to a writer which are pre-compressed\"\"\"\n        writer.add_read(random_read_pre_compressed)\n\n    def test_read_edit_write(self, reader: p5.Reader, writer: p5.Writer) -> None:\n        \"\"\"Read some records, edit the reads and write an edited read\"\"\"\n\n        records = 0\n        for record in reader:\n            records += 1\n            read = record.to_read()\n\n            # Edit some attributes\n            read.calibration = p5.Calibration(0, 1)\n            read.end_reason = p5.EndReason.from_reason_with_default_forced(\n                p5.EndReasonEnum.DATA_SERVICE_UNBLOCK_MUX_CHANGE\n            )\n            # Edit the signal\n            read.signal = np.arange(0, 100, dtype=np.int16)\n\n            # Write the edited read\n            writer.add_read(read)\n\n        writer.close()\n\n        edited = 0\n        for edited_record in p5.Reader(writer.path):\n            edited += 1\n            assert edited_record.calibration.offset == 0\n            assert edited_record.calibration.scale == 1\n            assert (\n                edited_record.end_reason\n                == p5.EndReason.from_reason_with_default_forced(\n                    p5.EndReasonEnum.DATA_SERVICE_UNBLOCK_MUX_CHANGE\n                )\n            )\n            assert len(edited_record.signal) == 100\n            assert min(edited_record.signal) == 0\n            assert max(edited_record.signal) == 99\n\n        assert edited == records\n\n    def test_read_copy(self, reader: p5.Reader, writer: p5.Writer) -> None:\n        \"\"\"Read some records, edit the reads and write an edited read\"\"\"\n\n        records = {}\n        for record in reader:\n            records[record.read_id] = record\n            read = record.to_read()\n            writer.add_read(read)\n        writer.close()\n\n        edited = {}\n        for edited_record in p5.Reader(writer.path):\n            edited[edited_record.read_id] = edited_record\n\n        assert len(records) == len(edited)\n        for read_id in records.keys():\n            before = records[read_id]\n            after = edited[read_id]\n\n            assert before.read_id == after.read_id\n            assert before.read_number == after.read_number\n            assert before.start_sample == after.start_sample\n            assert before.num_samples == after.num_samples\n            assert (\n                all(math.isnan(x) for x in (before.median_before, after.median_before))\n                or before.median_before == after.median_before\n            )\n            assert before.num_minknow_events == after.num_minknow_events\n            assert before.tracked_scaling == after.tracked_scaling\n            assert before.predicted_scaling == after.predicted_scaling\n            assert before.num_reads_since_mux_change == after.num_reads_since_mux_change\n            assert before.time_since_mux_change == after.time_since_mux_change\n            assert before.pore == after.pore\n            assert before.calibration == after.calibration\n            assert before.calibration_digitisation == after.calibration_digitisation\n            assert before.calibration_range == after.calibration_range\n            assert before.end_reason == after.end_reason\n            assert before.run_info == after.run_info\n            assert before.end_reason_index == after.end_reason_index\n            assert before.run_info_index == after.run_info_index\n            assert before.sample_count == after.sample_count\n            # assert before.byte_count == after.byte_count\n            assert before.has_cached_signal == after.has_cached_signal\n            assert np.array_equal(before.signal, after.signal)\n            assert np.array_equal(before.signal_pa, after.signal_pa)\n\n    def test_read_record_type_check(self, reader: p5.Reader, writer: p5.Writer) -> None:\n        \"\"\"Check type errors raised when passing ReadRecords to writer\"\"\"\n        with pytest.raises(TypeError, match=\"ReadRecord.to_read\"):\n            for record in reader:\n                writer.add_read(record)  # type: ignore\n\n        with pytest.raises(TypeError, match=\"ReadRecord.to_read\"):\n            writer.add_reads([r for r in reader])  # type: ignore\n\n        with pytest.raises(TypeError, match=\"unexpected type\"):\n            writer.add_read([1])  # type: ignore\n\n        writer.close()\n"
  },
  {
    "path": "python/pod5/test_utils/check_pod5_files_equal.py",
    "content": "import argparse\nimport itertools\nimport sys\nfrom pathlib import Path\n\nimport pod5 as p5\n\n\ndef main():\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"input_a\", type=Path)\n    parser.add_argument(\"input_b\", type=Path)\n\n    args = parser.parse_args()\n\n    file_a = p5.Reader(args.input_a)\n    file_b = p5.Reader(args.input_b)\n\n    fields = [\n        \"read_number\",\n        \"start_sample\",\n        \"median_before\",\n        \"pore\",\n        \"calibration\",\n        \"end_reason\",\n        \"run_info\",\n    ]\n\n    errors = 0\n    read_count = 0\n    for a, b in itertools.zip_longest(file_a.reads(), file_b.reads()):\n        read_count += 1\n\n        if a.read_id != b.read_id:\n            print(\n                f\"Different reads found in file at row {read_count}: {a.read_id} vs {b.read_id}\"\n            )\n            errors += 1\n\n        read_id = a.read_id\n\n        for field in fields:\n            a_val = getattr(a, field)\n            b_val = getattr(b, field)\n            # Handle NAN specially:\n            if a_val != a_val:\n                if b_val == b_val:\n                    print(\n                        f\"Read {read_id}: Field {field} not equal: {a_val} vs {b_val}\"\n                    )\n                    errors += 1\n            else:\n                if a_val != b_val:\n                    print(\n                        f\"Read {read_id}: Field {field} not equal: {a_val} vs {b_val}\"\n                    )\n                    errors += 1\n\n        if (a.signal != b.signal).any():\n            print(\n                f\"Read {read_count} {read_id} signal not equal: {len(a.signal)} elements:\"\n                f\" {a.signal} vs {len(b.signal)} elements: {b.signal}\"\n            )\n            errors += 1\n\n    if errors == 0:\n        print(\"Files consistent\")\n        sys.exit(0)\n\n    print(\"Errors detected\")\n    sys.exit(1)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "test_data/multi_fast5_zip_v0.pod5",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:9eb656f0061f1621b205b4c5f4d9694b03cad4480c66e2a2d8ea4423f53ea243\nsize 1321288\n"
  },
  {
    "path": "test_data/multi_fast5_zip_v1.pod5",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:64ec14c7483a0ebb812c7b09ee52e115927a4d2f693abc1ad27c9cc548b1e770\nsize 1322840\n"
  },
  {
    "path": "test_data/multi_fast5_zip_v2.pod5",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:b1cd3c85e8e2b8fdbc68c53ae8e8ca66655b95341be58abbba53353c5d7dba0a\nsize 1323072\n"
  },
  {
    "path": "test_data/multi_fast5_zip_v3.pod5",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:b87153aa81a5884205b047df336a2e15e95becce6dc997a38a76b972cf6a82bc\nsize 1323624\n"
  },
  {
    "path": "test_data/multi_fast5_zip_v4.pod5",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:9d0bdb7faef6ada8181eb620eb6f41256c6437680585b104fc19ee674c88d5f1\nsize 1323824\n"
  },
  {
    "path": "test_data/split_1_v4.pod5",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:5fe01201db83dc57fbb8f9ee1420fdeae8bb597f8dd6a57d3602b5d3d779fc80\nsize 151552\n"
  },
  {
    "path": "test_data/split_2_v4.pod5",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:900e68cb12055b38f938c440c8fec85f79be020ba7f3e147fcf6824514bc8915\nsize 142896\n"
  },
  {
    "path": "test_data/subset_mapping_examples/read_ids.txt",
    "content": "\n# empty lines and comment\n# example invalid uuid hidden by comment\n# 0000173c-bf67-44e7-9a9c-1ad0bc728e7a.\nread_id\n0000173c-bf67-44e7-9a9c-1ad0bc728e74\n00925f34-6baf-47fc-b40c-22591e27fb5c\n# Comment example\n"
  },
  {
    "path": "test_data/subset_mapping_examples/subset.csv",
    "content": "output_1.pod5,0000173c-bf67-44e7-9a9c-1ad0bc728e74\noutput_1.pod5,006d1319-2877-4b34-85df-34de7250a47b\noutput_2.pod5,00925f34-6baf-47fc-b40c-22591e27fb5c\noutput_2.pod5,009dc9bd-c5f4-487b-ba4c-b9ce7e3a711e\n"
  },
  {
    "path": "test_data/subset_mapping_examples/subset.summary",
    "content": "read_id mux barcode\n0000173c-bf67-44e7-9a9c-1ad0bc728e74\t1\tbarcode_1\n006d1319-2877-4b34-85df-34de7250a47b\t1\tbarcode_2\n00925f34-6baf-47fc-b40c-22591e27fb5c\t2\tbarcode_1\n009dc9bd-c5f4-487b-ba4c-b9ce7e3a711e\t2\tbarcode_2\n"
  },
  {
    "path": "test_package/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.8)\nproject(test_package CXX)\n\n# Test components\nfind_package(pod5_file_format REQUIRED CONFIG)\nadd_executable(${PROJECT_NAME} test_package.cpp test_cpp_api.cpp)\n\ntarget_include_directories(${PROJECT_NAME}\n    PUBLIC\n        ${CMAKE_SOURCE_DIR}/../third_party/include\n)\n\nset_target_properties(${PROJECT_NAME}\n    PROPERTIES\n        CXX_STANDARD 20\n)\n\ntarget_link_libraries(${PROJECT_NAME} pod5_file_format::pod5_file_format)\n"
  },
  {
    "path": "test_package/conanfile.py",
    "content": "import os\n\nfrom conan import ConanFile\nfrom conan.tools.build import can_run\nfrom conan.tools.cmake import cmake_layout, CMake\n\n\nclass TestPackageConan(ConanFile):\n    settings = \"os\", \"arch\", \"compiler\", \"build_type\"\n    options = {\n        \"compiler.sanitizer\": [\n            None,\n            \"AddressStatic\",\n            \"ThreadStatic\",\n            \"UndefinedBehaviorStatic\",\n        ]\n    }\n    default_options = {\"compiler.sanitizer\": None}\n    generators = \"CMakeDeps\", \"CMakeToolchain\", \"VirtualRunEnv\"\n    test_type = \"explicit\"\n\n    def requirements(self):\n        self.requires(self.tested_reference_str)\n\n    def layout(self):\n        cmake_layout(self)\n\n    def build(self):\n        cmake = CMake(self)\n        cmake.configure()\n        cmake.build()\n\n    @property\n    def _test_executable(self):\n        return os.path.join(self.cpp.build.bindirs[0], \"test_package\")\n\n    def test(self):\n        if can_run(self):\n            self.run(self._test_executable, env=\"conanrun\")\n        else:\n            self.output.warn(\"Pod5Conan test: cross_building is true\")\n"
  },
  {
    "path": "test_package/test_cpp_api.cpp",
    "content": "#include \"pod5_format/file_writer.h\"\n#include \"pod5_format/read_table_writer.h\"\n#include \"pod5_format/run_info_table_writer.h\"\n#include \"pod5_format/signal_table_writer.h\"\n"
  },
  {
    "path": "test_package/test_package.cpp",
    "content": "#include \"pod5_format/c_api.h\"\n\n#include <iostream>\n\nint main()\n{\n    std::cout << \"Initializing POD5....\" << std::endl;\n    if (pod5_init() == POD5_OK) {\n        std::cout << \"Pod5 successfully initialized.\" << std::endl;\n    } else {\n        std::cerr << \"Failed to initialize Pod5!\" << std::endl;\n    }\n\n    std::cout << \"Shutting down POD5 gracefully....\" << std::endl;\n    if (pod5_terminate() == POD5_OK) {\n        std::cout << \"Pod5 successfully terminated.\" << std::endl;\n    } else {\n        std::cerr << \"Failed to shut down Pod5!\" << std::endl;\n    }\n}\n"
  },
  {
    "path": "third_party/build_instructions.txt",
    "content": "These instructions provide details on how to re-create the third party support libraries from source.\n\nFor each library, see software_version.yaml for download links, versions and license information.\n\nThese instructions all assume you have set the THIRD_PARTY_LIBS env var:\n\nTHIRD_PARTY_LIBS=/path/to/bass/third_party\nmkdir -p $THIRD_PARTY_LIBS/include\n\n\nCatch2\n======\n\nDownload catch.hpp from the release and put it in $THIRD_PARTY_LIBS/include/catch2\n\n\nGSL Lite\n========\n\nDownload and extract tarball. From a bash prompt, cd into the directory and run:\n\nApply the gsl patches in $THIRD_PARTY_LIBS:\n    for p in $THIRD_PARTY_LIBS/gsl-*.patch; do\n        patch -Np1 -i $p\n    done\n\nCopy the headers:\n    rm -rf $THIRD_PARTY_LIBS/include/gsl $THIRD_PARTY_LIBS/include/gsl.h\n    cp -r include/* $THIRD_PARTY_LIBS/include/\n"
  },
  {
    "path": "third_party/gsl-disable-gsl-suppress.patch",
    "content": "--- a/include/gsl/gsl-lite.hpp\n+++ b/include/gsl/gsl-lite.hpp\n@@ -1081,7 +1081,7 @@ namespace __cxxabiv1 { struct __cxa_eh_globals; extern \"C\" __cxa_eh_globals * __\n // MSVC warning suppression macros:\n\n #if gsl_COMPILER_MSVC_VERSION >= 140 && ! gsl_COMPILER_NVCC_VERSION\n-# define gsl_SUPPRESS_MSGSL_WARNING(expr)        [[gsl::suppress(expr)]]\n+# define gsl_SUPPRESS_MSGSL_WARNING(expr)        /* Pimm: note disabled for intel [[gsl::suppress(expr)]]*/\n # define gsl_SUPPRESS_MSVC_WARNING(code, descr)  __pragma(warning(suppress: code) )\n # define gsl_DISABLE_MSVC_WARNINGS(codes)        __pragma(warning(push))  __pragma(warning(disable: codes))\n # define gsl_RESTORE_MSVC_WARNINGS()             __pragma(warning(pop ))\n"
  },
  {
    "path": "third_party/include/.editorconfig",
    "content": "# All code in this directory is third-party - don't enforce any particular settings\nroot = true\n"
  },
  {
    "path": "third_party/include/catch2/catch.hpp",
    "content": "/*\n *  Catch v2.13.7\n *  Generated: 2021-07-28 20:29:27.753164\n *  ----------------------------------------------------------\n *  This file has been merged from multiple headers. Please don't edit it\n * directly Copyright (c) 2021 Two Blue Cubes Ltd. All rights reserved.\n *\n *  Distributed under the Boost Software License, Version 1.0. (See accompanying\n *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n */\n#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n// start catch.hpp\n\n#define CATCH_VERSION_MAJOR 2\n#define CATCH_VERSION_MINOR 13\n#define CATCH_VERSION_PATCH 7\n\n#ifdef __clang__\n#pragma clang system_header\n#elif defined __GNUC__\n#pragma GCC system_header\n#endif\n\n// start catch_suppress_warnings.h\n\n#ifdef __clang__\n#ifdef __ICC  // icpc defines the __clang__ macro\n#pragma warning(push)\n#pragma warning(disable : 161 1682)\n#else  // __ICC\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpadded\"\n#pragma clang diagnostic ignored \"-Wswitch-enum\"\n#pragma clang diagnostic ignored \"-Wcovered-switch-default\"\n#endif\n#elif defined __GNUC__\n// Because REQUIREs trigger GCC's -Wparentheses, and because still\n// supported version of g++ have only buggy support for _Pragmas,\n// Wparentheses have to be suppressed globally.\n#pragma GCC diagnostic ignored \"-Wparentheses\"  // See #674 for details\n\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wunused-variable\"\n#pragma GCC diagnostic ignored \"-Wpadded\"\n#endif\n// end catch_suppress_warnings.h\n#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER)\n#define CATCH_IMPL\n#define CATCH_CONFIG_ALL_PARTS\n#endif\n\n// In the impl file, we want to have access to all parts of the headers\n// Can also be used to sanely support PCHs\n#if defined(CATCH_CONFIG_ALL_PARTS)\n#define CATCH_CONFIG_EXTERNAL_INTERFACES\n#if defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#undef CATCH_CONFIG_DISABLE_MATCHERS\n#endif\n#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)\n#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER\n#endif\n#endif\n\n#if !defined(CATCH_CONFIG_IMPL_ONLY)\n// start catch_platform.h\n\n// See e.g.:\n// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html\n#ifdef __APPLE__\n#include <TargetConditionals.h>\n#if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1)\n#define CATCH_PLATFORM_MAC\n#elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1)\n#define CATCH_PLATFORM_IPHONE\n#endif\n\n#elif defined(linux) || defined(__linux) || defined(__linux__)\n#define CATCH_PLATFORM_LINUX\n\n#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || \\\n        defined(__MINGW32__)\n#define CATCH_PLATFORM_WINDOWS\n#endif\n\n// end catch_platform.h\n\n#ifdef CATCH_IMPL\n#ifndef CLARA_CONFIG_MAIN\n#define CLARA_CONFIG_MAIN_NOT_DEFINED\n#define CLARA_CONFIG_MAIN\n#endif\n#endif\n\n// start catch_user_interfaces.h\n\nnamespace Catch {\nunsigned int rngSeed();\n}\n\n// end catch_user_interfaces.h\n// start catch_tag_alias_autoregistrar.h\n\n// start catch_common.h\n\n// start catch_compiler_capabilities.h\n\n// Detect a number of compiler features - by compiler\n// The following features are defined:\n//\n// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported?\n// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported?\n// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported?\n// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled?\n// ****************\n// Note to maintainers: if new toggles are added please document them\n// in configuration.md, too\n// ****************\n\n// In general each macro has a _NO_<feature name> form\n// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature.\n// Many features, at point of detection, define an _INTERNAL_ macro, so they\n// can be combined, en-mass, with the _NO_ forms later.\n\n#ifdef __cplusplus\n\n#if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)\n#define CATCH_CPP14_OR_GREATER\n#endif\n\n#if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)\n#define CATCH_CPP17_OR_GREATER\n#endif\n\n#endif\n\n// Only GCC compiler should be used in this block, so other compilers trying to\n// mask themselves as GCC should be ignored.\n#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && \\\n        !defined(__LCC__)\n#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma(\"GCC diagnostic push\")\n#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma(\"GCC diagnostic pop\")\n\n#define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__)\n\n#endif\n\n#if defined(__clang__)\n\n#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma(\"clang diagnostic push\")\n#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma(\"clang diagnostic pop\")\n\n// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug\n// which results in calls to destructors being emitted for each temporary,\n// without a matching initialization. In practice, this can result in something\n// like `std::string::~string` being called on an uninitialized value.\n//\n// For example, this code will likely segfault under IBM XL:\n// ```\n// REQUIRE(std::string(\"12\") + \"34\" == \"1234\")\n// ```\n//\n// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented.\n#if !defined(__ibmxl__) && !defined(__CUDACC__)\n#define CATCH_INTERNAL_IGNORE_BUT_WARN(...) \\\n    (void)__builtin_constant_p(             \\\n            __VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg,                \\\n                      hicpp-vararg) */\n#endif\n\n#define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                    \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wexit-time-destructors\\\"\") \\\n            _Pragma(\"clang diagnostic ignored \\\"-Wglobal-constructors\\\"\")\n\n#define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wparentheses\\\"\")\n\n#define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wunused-variable\\\"\")\n\n#define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wgnu-zero-variadic-macro-arguments\\\"\")\n\n#define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wunused-template\\\"\")\n\n#endif  // __clang__\n\n////////////////////////////////////////////////////////////////////////////////\n// Assume that non-Windows platforms support posix signals by default\n#if !defined(CATCH_PLATFORM_WINDOWS)\n#define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// We know some environments not to support full POSIX signals\n#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__)\n#define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS\n#endif\n\n#ifdef __OS400__\n#define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS\n#define CATCH_CONFIG_COLOUR_NONE\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// Android somehow still does not support std::to_string\n#if defined(__ANDROID__)\n#define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING\n#define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// Not all Windows environments support SEH properly\n#if defined(__MINGW32__)\n#define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// PS4\n#if defined(__ORBIS__)\n#define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// Cygwin\n#ifdef __CYGWIN__\n\n// Required for some versions of Cygwin to declare gettimeofday\n// see:\n// http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin\n#define _BSD_SOURCE\n// some versions of cygwin (most) do not support std::to_string. Use the libstd\n// check.\n// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html\n// line 2812-2813\n#if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) && \\\n      !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF))\n\n#define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING\n\n#endif\n#endif  // __CYGWIN__\n\n////////////////////////////////////////////////////////////////////////////////\n// Visual C++\n#if defined(_MSC_VER)\n\n#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma(warning(push))\n#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma(warning(pop))\n\n// Universal Windows platform does not support SEH\n// Or console colours (or console at all...)\n#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)\n#define CATCH_CONFIG_COLOUR_NONE\n#else\n#define CATCH_INTERNAL_CONFIG_WINDOWS_SEH\n#endif\n\n// MSVC traditional preprocessor needs some workaround for __VA_ARGS__\n// _MSVC_TRADITIONAL == 0 means new conformant preprocessor\n// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor\n#if !defined(__clang__)  // Handle Clang masquerading for msvc\n#if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL)\n#define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#endif  // MSVC_TRADITIONAL\n#endif  // __clang__\n\n#endif  // _MSC_VER\n\n#if defined(_REENTRANT) || defined(_MSC_VER)\n// Enable async processing, as -pthread is specified or no additional linking is\n// required\n#define CATCH_INTERNAL_CONFIG_USE_ASYNC\n#endif  // _MSC_VER\n\n////////////////////////////////////////////////////////////////////////////////\n// Check if we are compiled with -fno-exceptions or equivalent\n#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND)\n#define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// DJGPP\n#ifdef __DJGPP__\n#define CATCH_INTERNAL_CONFIG_NO_WCHAR\n#endif  // __DJGPP__\n\n////////////////////////////////////////////////////////////////////////////////\n// Embarcadero C++Build\n#if defined(__BORLANDC__)\n#define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n\n// Use of __COUNTER__ is suppressed during code analysis in\n// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly\n// handled by it.\n// Otherwise all supported compilers support COUNTER macro,\n// but user still might want to turn it off\n#if (!defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L)\n#define CATCH_INTERNAL_CONFIG_COUNTER\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n\n// RTX is a special version of Windows that is real time.\n// This means that it is detected as Windows, but does not provide\n// the same set of capabilities as real Windows does.\n#if defined(UNDER_RTSS) || defined(RTX64_BUILD)\n#define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH\n#define CATCH_INTERNAL_CONFIG_NO_ASYNC\n#define CATCH_CONFIG_COLOUR_NONE\n#endif\n\n#if !defined(_GLIBCXX_USE_C99_MATH_TR1)\n#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER\n#endif\n\n// Various stdlib support checks that require __has_include\n#if defined(__has_include)\n// Check if string_view is available and usable\n#if __has_include(<string_view>) && defined(CATCH_CPP17_OR_GREATER)\n#define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW\n#endif\n\n// Check if optional is available and usable\n#if __has_include(<optional>) && defined(CATCH_CPP17_OR_GREATER)\n#define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL\n#endif  // __has_include(<optional>) && defined(CATCH_CPP17_OR_GREATER)\n\n// Check if byte is available and usable\n#if __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER)\n#include <cstddef>\n#if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0)\n#define CATCH_INTERNAL_CONFIG_CPP17_BYTE\n#endif\n#endif  // __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER)\n\n// Check if variant is available and usable\n#if __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER)\n#if defined(__clang__) && (__clang_major__ < 8)\n// work around clang bug with libstdc++\n// https://bugs.llvm.org/show_bug.cgi?id=31852 fix should be in clang 8,\n// workaround in libstdc++ 8.2\n#include <ciso646>\n#if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9)\n#define CATCH_CONFIG_NO_CPP17_VARIANT\n#else\n#define CATCH_INTERNAL_CONFIG_CPP17_VARIANT\n#endif  // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE \\\n        // < 9)\n#else\n#define CATCH_INTERNAL_CONFIG_CPP17_VARIANT\n#endif  // defined(__clang__) && (__clang_major__ < 8)\n#endif  // __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER)\n#endif  // defined(__has_include)\n\n#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && \\\n        !defined(CATCH_CONFIG_COUNTER)\n#define CATCH_CONFIG_COUNTER\n#endif\n#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && \\\n        !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH)\n#define CATCH_CONFIG_WINDOWS_SEH\n#endif\n// This is set by default, because we assume that unix compilers are\n// posix-signal-compatible by default.\n#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) &&         \\\n        !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && \\\n        !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS)\n#define CATCH_CONFIG_POSIX_SIGNALS\n#endif\n// This is set by default, because we assume that compilers with no wchar_t\n// support are just rare exceptions.\n#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && \\\n        !defined(CATCH_CONFIG_WCHAR)\n#define CATCH_CONFIG_WCHAR\n#endif\n\n#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && \\\n        !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING)\n#define CATCH_CONFIG_CPP11_TO_STRING\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && \\\n        !defined(CATCH_CONFIG_CPP17_OPTIONAL)\n#define CATCH_CONFIG_CPP17_OPTIONAL\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && \\\n        !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW)\n#define CATCH_CONFIG_CPP17_STRING_VIEW\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && \\\n        !defined(CATCH_CONFIG_CPP17_VARIANT)\n#define CATCH_CONFIG_CPP17_VARIANT\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && \\\n        !defined(CATCH_CONFIG_CPP17_BYTE)\n#define CATCH_CONFIG_CPP17_BYTE\n#endif\n\n#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)\n#define CATCH_INTERNAL_CONFIG_NEW_CAPTURE\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) &&                                                  \\\n        !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && \\\n        !defined(CATCH_CONFIG_NEW_CAPTURE)\n#define CATCH_CONFIG_NEW_CAPTURE\n#endif\n\n#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n#define CATCH_CONFIG_DISABLE_EXCEPTIONS\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && \\\n        !defined(CATCH_CONFIG_POLYFILL_ISNAN)\n#define CATCH_CONFIG_POLYFILL_ISNAN\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && \\\n        !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC)\n#define CATCH_CONFIG_USE_ASYNC\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && \\\n        !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE)\n#define CATCH_CONFIG_ANDROID_LOGWRITE\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && \\\n        !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)\n#define CATCH_CONFIG_GLOBAL_NEXTAFTER\n#endif\n\n// Even if we do not think the compiler has that warning, we still have\n// to provide a macro that can be used by the code.\n#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION)\n#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION\n#endif\n#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION)\n#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n#endif\n#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS)\n#define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS\n#endif\n#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS)\n#define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS\n#endif\n#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS)\n#define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS\n#endif\n#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS)\n#define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS\n#endif\n\n// The goal of this macro is to avoid evaluation of the arguments, but\n// still have the compiler warn on problems inside...\n#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN)\n#define CATCH_INTERNAL_IGNORE_BUT_WARN(...)\n#endif\n\n#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10)\n#undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS\n#elif defined(__clang__) && (__clang_major__ < 5)\n#undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS\n#endif\n\n#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS)\n#define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS\n#endif\n\n#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n#define CATCH_TRY if ((true))\n#define CATCH_CATCH_ALL if ((false))\n#define CATCH_CATCH_ANON(type) if ((false))\n#else\n#define CATCH_TRY try\n#define CATCH_CATCH_ALL catch (...)\n#define CATCH_CATCH_ANON(type) catch (type)\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && \\\n        !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) &&  \\\n        !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR)\n#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#endif\n\n// end catch_compiler_capabilities.h\n#define INTERNAL_CATCH_UNIQUE_NAME_LINE2(name, line) name##line\n#define INTERNAL_CATCH_UNIQUE_NAME_LINE(name, line) INTERNAL_CATCH_UNIQUE_NAME_LINE2(name, line)\n#ifdef CATCH_CONFIG_COUNTER\n#define INTERNAL_CATCH_UNIQUE_NAME(name) INTERNAL_CATCH_UNIQUE_NAME_LINE(name, __COUNTER__)\n#else\n#define INTERNAL_CATCH_UNIQUE_NAME(name) INTERNAL_CATCH_UNIQUE_NAME_LINE(name, __LINE__)\n#endif\n\n#include <cstdint>\n#include <iosfwd>\n#include <string>\n\n// We need a dummy global operator<< so we can bring it into Catch namespace\n// later\nstruct Catch_global_namespace_dummy {};\nstd::ostream &operator<<(std::ostream &, Catch_global_namespace_dummy);\n\nnamespace Catch {\n\nstruct CaseSensitive {\n    enum Choice { Yes, No };\n};\n\nclass NonCopyable {\n    NonCopyable(NonCopyable const &) = delete;\n    NonCopyable(NonCopyable &&) = delete;\n    NonCopyable &operator=(NonCopyable const &) = delete;\n    NonCopyable &operator=(NonCopyable &&) = delete;\n\nprotected:\n    NonCopyable();\n    virtual ~NonCopyable();\n};\n\nstruct SourceLineInfo {\n    SourceLineInfo() = delete;\n    SourceLineInfo(char const *_file, std::size_t _line) noexcept : file(_file), line(_line) {}\n\n    SourceLineInfo(SourceLineInfo const &other) = default;\n    SourceLineInfo &operator=(SourceLineInfo const &) = default;\n    SourceLineInfo(SourceLineInfo &&) noexcept = default;\n    SourceLineInfo &operator=(SourceLineInfo &&) noexcept = default;\n\n    bool empty() const noexcept { return file[0] == '\\0'; }\n    bool operator==(SourceLineInfo const &other) const noexcept;\n    bool operator<(SourceLineInfo const &other) const noexcept;\n\n    char const *file;\n    std::size_t line;\n};\n\nstd::ostream &operator<<(std::ostream &os, SourceLineInfo const &info);\n\n// Bring in operator<< from global namespace into Catch namespace\n// This is necessary because the overload of operator<< above makes\n// lookup stop at namespace Catch\nusing ::operator<<;\n\n// Use this in variadic streaming macros to allow\n//    >> +StreamEndStop\n// as well as\n//    >> stuff +StreamEndStop\nstruct StreamEndStop {\n    std::string operator+() const;\n};\ntemplate <typename T>\nT const &operator+(T const &value, StreamEndStop) {\n    return value;\n}\n}  // namespace Catch\n\n#define CATCH_INTERNAL_LINEINFO \\\n    ::Catch::SourceLineInfo(__FILE__, static_cast<std::size_t>(__LINE__))\n\n// end catch_common.h\nnamespace Catch {\n\nstruct RegistrarForTagAliases {\n    RegistrarForTagAliases(char const *alias, char const *tag, SourceLineInfo const &lineInfo);\n};\n\n}  // end namespace Catch\n\n#define CATCH_REGISTER_TAG_ALIAS(alias, spec)                            \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                            \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                             \\\n    namespace {                                                          \\\n    Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME(            \\\n            AutoRegisterTagAlias)(alias, spec, CATCH_INTERNAL_LINEINFO); \\\n    }                                                                    \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n// end catch_tag_alias_autoregistrar.h\n// start catch_test_registry.h\n\n// start catch_interfaces_testcase.h\n\n#include <vector>\n\nnamespace Catch {\n\nclass TestSpec;\n\nstruct ITestInvoker {\n    virtual void invoke() const = 0;\n    virtual ~ITestInvoker();\n};\n\nclass TestCase;\nstruct IConfig;\n\nstruct ITestCaseRegistry {\n    virtual ~ITestCaseRegistry();\n    virtual std::vector<TestCase> const &getAllTests() const = 0;\n    virtual std::vector<TestCase> const &getAllTestsSorted(IConfig const &config) const = 0;\n};\n\nbool isThrowSafe(TestCase const &testCase, IConfig const &config);\nbool matchTest(TestCase const &testCase, TestSpec const &testSpec, IConfig const &config);\nstd::vector<TestCase> filterTests(std::vector<TestCase> const &testCases,\n                                  TestSpec const &testSpec,\n                                  IConfig const &config);\nstd::vector<TestCase> const &getAllTestCasesSorted(IConfig const &config);\n\n}  // namespace Catch\n\n// end catch_interfaces_testcase.h\n// start catch_stringref.h\n\n#include <cassert>\n#include <cstddef>\n#include <iosfwd>\n#include <string>\n\nnamespace Catch {\n\n/// A non-owning string class (similar to the forthcoming std::string_view)\n/// Note that, because a StringRef may be a substring of another string,\n/// it may not be null terminated.\nclass StringRef {\npublic:\n    using size_type = std::size_t;\n    using const_iterator = const char *;\n\nprivate:\n    static constexpr char const *const s_empty = \"\";\n\n    char const *m_start = s_empty;\n    size_type m_size = 0;\n\npublic:  // construction\n    constexpr StringRef() noexcept = default;\n\n    StringRef(char const *rawChars) noexcept;\n\n    constexpr StringRef(char const *rawChars, size_type size) noexcept\n            : m_start(rawChars), m_size(size) {}\n\n    StringRef(std::string const &stdString) noexcept\n            : m_start(stdString.c_str()), m_size(stdString.size()) {}\n\n    explicit operator std::string() const { return std::string(m_start, m_size); }\n\npublic:  // operators\n    auto operator==(StringRef const &other) const noexcept -> bool;\n    auto operator!=(StringRef const &other) const noexcept -> bool { return !(*this == other); }\n\n    auto operator[](size_type index) const noexcept -> char {\n        assert(index < m_size);\n        return m_start[index];\n    }\n\npublic:  // named queries\n    constexpr auto empty() const noexcept -> bool { return m_size == 0; }\n    constexpr auto size() const noexcept -> size_type { return m_size; }\n\n    // Returns the current start pointer. If the StringRef is not\n    // null-terminated, throws std::domain_exception\n    auto c_str() const -> char const *;\n\npublic:  // substrings and searches\n    // Returns a substring of [start, start + length).\n    // If start + length > size(), then the substring is [start, size()).\n    // If start > size(), then the substring is empty.\n    auto substr(size_type start, size_type length) const noexcept -> StringRef;\n\n    // Returns the current start pointer. May not be null-terminated.\n    auto data() const noexcept -> char const *;\n\n    constexpr auto isNullTerminated() const noexcept -> bool { return m_start[m_size] == '\\0'; }\n\npublic:  // iterators\n    constexpr const_iterator begin() const { return m_start; }\n    constexpr const_iterator end() const { return m_start + m_size; }\n};\n\nauto operator+=(std::string &lhs, StringRef const &sr) -> std::string &;\nauto operator<<(std::ostream &os, StringRef const &sr) -> std::ostream &;\n\nconstexpr auto operator\"\" _sr(char const *rawChars, std::size_t size) noexcept -> StringRef {\n    return StringRef(rawChars, size);\n}\n}  // namespace Catch\n\nconstexpr auto operator\"\" _catch_sr(char const *rawChars, std::size_t size) noexcept\n        -> Catch::StringRef {\n    return Catch::StringRef(rawChars, size);\n}\n\n// end catch_stringref.h\n// start catch_preprocessor.hpp\n\n#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__\n#define CATCH_RECURSION_LEVEL1(...) \\\n    CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL2(...) \\\n    CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL3(...) \\\n    CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL4(...) \\\n    CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL5(...) \\\n    CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__)))\n\n#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__\n// MSVC needs more evaluations\n#define CATCH_RECURSION_LEVEL6(...) \\\n    CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__)))\n#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__))\n#else\n#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__)\n#endif\n\n#define CATCH_REC_END(...)\n#define CATCH_REC_OUT\n\n#define CATCH_EMPTY()\n#define CATCH_DEFER(id) id CATCH_EMPTY()\n\n#define CATCH_REC_GET_END2() 0, CATCH_REC_END\n#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2\n#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1\n#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT\n#define CATCH_REC_NEXT1(test, next) CATCH_DEFER(CATCH_REC_NEXT0)(test, next, 0)\n#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next)\n\n#define CATCH_REC_LIST0(f, x, peek, ...) \\\n    , f(x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1))(f, peek, __VA_ARGS__)\n#define CATCH_REC_LIST1(f, x, peek, ...) \\\n    , f(x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST0))(f, peek, __VA_ARGS__)\n#define CATCH_REC_LIST2(f, x, peek, ...) \\\n    f(x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1))(f, peek, __VA_ARGS__)\n\n#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...)                                         \\\n    , f(userdata, x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD))(f, userdata, peek, \\\n                                                                           __VA_ARGS__)\n#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...)                                         \\\n    , f(userdata, x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD))(f, userdata, peek, \\\n                                                                           __VA_ARGS__)\n#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) \\\n    f(userdata, x)                                    \\\n            CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD))(f, userdata, peek, __VA_ARGS__)\n\n// Applies the function macro `f` to each of the remaining parameters, inserts\n// commas between the results, and passes userdata as the first parameter to\n// each invocation, e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a),\n// f(x, b), f(x, c)\n#define CATCH_REC_LIST_UD(f, userdata, ...) \\\n    CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0))\n\n#define CATCH_REC_LIST(f, ...) \\\n    CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))\n\n#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param)\n#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO##__VA_ARGS__\n#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__\n#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF\n#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__)\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__\n#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) \\\n    INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param))\n#else\n// MSVC is adding extra space and needs another indirection to expand\n// INTERNAL_CATCH_NOINTERNAL_CATCH_DEF\n#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__)\n#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__\n#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) \\\n    (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1)\n#endif\n\n#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__\n#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name)\n\n#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__)\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) \\\n    decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>())\n#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) \\\n    INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))\n#else\n#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(            \\\n            decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>()))\n#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(           \\\n            INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)))\n#endif\n\n#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...) \\\n    CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST, __VA_ARGS__)\n\n#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0)\n#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) \\\n    INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1)\n#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) \\\n    INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2)\n#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) \\\n    INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3)\n#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) \\\n    INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4)\n#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) \\\n    INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5)\n#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) \\\n    INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6)\n#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) \\\n    INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7)\n#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) \\\n    INTERNAL_CATCH_REMOVE_PARENS(_0),                                          \\\n            INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8)\n#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \\\n    INTERNAL_CATCH_REMOVE_PARENS(_0),                                               \\\n            INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9)\n#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \\\n    INTERNAL_CATCH_REMOVE_PARENS(_0),                                                    \\\n            INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10)\n\n#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N\n\n#define INTERNAL_CATCH_TYPE_GEN                                                                 \\\n    template <typename...>                                                                      \\\n    struct TypeList {};                                                                         \\\n    template <typename... Ts>                                                                   \\\n    constexpr auto get_wrapper() noexcept->TypeList<Ts...> {                                    \\\n        return {};                                                                              \\\n    }                                                                                           \\\n    template <template <typename...> class...>                                                  \\\n    struct TemplateTypeList {};                                                                 \\\n    template <template <typename...> class... Cs>                                               \\\n    constexpr auto get_wrapper() noexcept->TemplateTypeList<Cs...> {                            \\\n        return {};                                                                              \\\n    }                                                                                           \\\n    template <typename...>                                                                      \\\n    struct append;                                                                              \\\n    template <typename...>                                                                      \\\n    struct rewrap;                                                                              \\\n    template <template <typename...> class, typename...>                                        \\\n    struct create;                                                                              \\\n    template <template <typename...> class, typename>                                           \\\n    struct convert;                                                                             \\\n                                                                                                \\\n    template <typename T>                                                                       \\\n    struct append<T> {                                                                          \\\n        using type = T;                                                                         \\\n    };                                                                                          \\\n    template <template <typename...> class L1, typename... E1, template <typename...> class L2, \\\n              typename... E2, typename... Rest>                                                 \\\n    struct append<L1<E1...>, L2<E2...>, Rest...> {                                              \\\n        using type = typename append<L1<E1..., E2...>, Rest...>::type;                          \\\n    };                                                                                          \\\n    template <template <typename...> class L1, typename... E1, typename... Rest>                \\\n    struct append<L1<E1...>, TypeList<mpl_::na>, Rest...> {                                     \\\n        using type = L1<E1...>;                                                                 \\\n    };                                                                                          \\\n                                                                                                \\\n    template <template <typename...> class Container, template <typename...> class List,        \\\n              typename... elems>                                                                \\\n    struct rewrap<TemplateTypeList<Container>, List<elems...>> {                                \\\n        using type = TypeList<Container<elems...>>;                                             \\\n    };                                                                                          \\\n    template <template <typename...> class Container, template <typename...> class List,        \\\n              class... Elems, typename... Elements>                                             \\\n    struct rewrap<TemplateTypeList<Container>, List<Elems...>, Elements...> {                   \\\n        using type = typename append<                                                           \\\n                TypeList<Container<Elems...>>,                                                  \\\n                typename rewrap<TemplateTypeList<Container>, Elements...>::type>::type;         \\\n    };                                                                                          \\\n                                                                                                \\\n    template <template <typename...> class Final, template <typename...> class... Containers,   \\\n              typename... Types>                                                                \\\n    struct create<Final, TemplateTypeList<Containers...>, TypeList<Types...>> {                 \\\n        using type = typename append<                                                           \\\n                Final<>,                                                                        \\\n                typename rewrap<TemplateTypeList<Containers>, Types...>::type...>::type;        \\\n    };                                                                                          \\\n    template <template <typename...> class Final, template <typename...> class List,            \\\n              typename... Ts>                                                                   \\\n    struct convert<Final, List<Ts...>> {                                                        \\\n        using type = typename append<Final<>, TypeList<Ts>...>::type;                           \\\n    };\n\n#define INTERNAL_CATCH_NTTP_1(signature, ...)                                                \\\n    template <INTERNAL_CATCH_REMOVE_PARENS(signature)>                                       \\\n    struct Nttp {};                                                                          \\\n    template <INTERNAL_CATCH_REMOVE_PARENS(signature)>                                       \\\n    constexpr auto get_wrapper() noexcept->Nttp<__VA_ARGS__> {                               \\\n        return {};                                                                           \\\n    }                                                                                        \\\n    template <template <INTERNAL_CATCH_REMOVE_PARENS(signature)> class...>                   \\\n    struct NttpTemplateTypeList {};                                                          \\\n    template <template <INTERNAL_CATCH_REMOVE_PARENS(signature)> class... Cs>                \\\n    constexpr auto get_wrapper() noexcept->NttpTemplateTypeList<Cs...> {                     \\\n        return {};                                                                           \\\n    }                                                                                        \\\n                                                                                             \\\n    template <template <INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container,            \\\n              template <INTERNAL_CATCH_REMOVE_PARENS(signature)> class List,                 \\\n              INTERNAL_CATCH_REMOVE_PARENS(signature)>                                       \\\n    struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>> {                      \\\n        using type = TypeList<Container<__VA_ARGS__>>;                                       \\\n    };                                                                                       \\\n    template <template <INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container,            \\\n              template <INTERNAL_CATCH_REMOVE_PARENS(signature)> class List,                 \\\n              INTERNAL_CATCH_REMOVE_PARENS(signature), typename... Elements>                 \\\n    struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>, Elements...> {         \\\n        using type = typename append<                                                        \\\n                TypeList<Container<__VA_ARGS__>>,                                            \\\n                typename rewrap<NttpTemplateTypeList<Container>, Elements...>::type>::type;  \\\n    };                                                                                       \\\n    template <template <typename...> class Final,                                            \\\n              template <INTERNAL_CATCH_REMOVE_PARENS(signature)> class... Containers,        \\\n              typename... Types>                                                             \\\n    struct create<Final, NttpTemplateTypeList<Containers...>, TypeList<Types...>> {          \\\n        using type = typename append<                                                        \\\n                Final<>,                                                                     \\\n                typename rewrap<NttpTemplateTypeList<Containers>, Types...>::type...>::type; \\\n    };\n\n#define INTERNAL_CATCH_DECLARE_SIG_TEST0(TestName)\n#define INTERNAL_CATCH_DECLARE_SIG_TEST1(TestName, signature) \\\n    template <INTERNAL_CATCH_REMOVE_PARENS(signature)>        \\\n    static void TestName()\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_X(TestName, signature, ...) \\\n    template <INTERNAL_CATCH_REMOVE_PARENS(signature)>              \\\n    static void TestName()\n\n#define INTERNAL_CATCH_DEFINE_SIG_TEST0(TestName)\n#define INTERNAL_CATCH_DEFINE_SIG_TEST1(TestName, signature) \\\n    template <INTERNAL_CATCH_REMOVE_PARENS(signature)>       \\\n    static void TestName()\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_X(TestName, signature, ...) \\\n    template <INTERNAL_CATCH_REMOVE_PARENS(signature)>             \\\n    static void TestName()\n\n#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature)                               \\\n    template <typename Type>                                                             \\\n    void reg_test(TypeList<Type>, Catch::NameAndTags nameAndTags) {                      \\\n        Catch::AutoReg(Catch::makeTestInvoker(&TestFunc<Type>), CATCH_INTERNAL_LINEINFO, \\\n                       Catch::StringRef(), nameAndTags);                                 \\\n    }\n\n#define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...)                                  \\\n    template <INTERNAL_CATCH_REMOVE_PARENS(signature)>                                          \\\n    void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags) {                          \\\n        Catch::AutoReg(Catch::makeTestInvoker(&TestFunc<__VA_ARGS__>), CATCH_INTERNAL_LINEINFO, \\\n                       Catch::StringRef(), nameAndTags);                                        \\\n    }\n\n#define INTERNAL_CATCH_NTTP_REGISTER_METHOD0(TestName, signature, ...)                          \\\n    template <typename Type>                                                                    \\\n    void reg_test(TypeList<Type>, Catch::StringRef className, Catch::NameAndTags nameAndTags) { \\\n        Catch::AutoReg(Catch::makeTestInvoker(&TestName<Type>::test), CATCH_INTERNAL_LINEINFO,  \\\n                       className, nameAndTags);                                                 \\\n    }\n\n#define INTERNAL_CATCH_NTTP_REGISTER_METHOD(TestName, signature, ...)                              \\\n    template <INTERNAL_CATCH_REMOVE_PARENS(signature)>                                             \\\n    void reg_test(Nttp<__VA_ARGS__>, Catch::StringRef className, Catch::NameAndTags nameAndTags) { \\\n        Catch::AutoReg(Catch::makeTestInvoker(&TestName<__VA_ARGS__>::test),                       \\\n                       CATCH_INTERNAL_LINEINFO, className, nameAndTags);                           \\\n    }\n\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0(TestName, ClassName)\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1(TestName, ClassName, signature) \\\n    template <typename TestType>                                                \\\n    struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<TestType> {       \\\n        void test();                                                            \\\n    }\n\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X(TestName, ClassName, signature, ...) \\\n    template <INTERNAL_CATCH_REMOVE_PARENS(signature)>                                \\\n    struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<__VA_ARGS__> {          \\\n        void test();                                                                  \\\n    }\n\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0(TestName)\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1(TestName, signature) \\\n    template <typename TestType>                                    \\\n    void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<TestType>::test()\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X(TestName, signature, ...) \\\n    template <INTERNAL_CATCH_REMOVE_PARENS(signature)>                    \\\n    void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<__VA_ARGS__>::test()\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_NTTP_0\n#define INTERNAL_CATCH_NTTP_GEN(...)                                                             \\\n    INTERNAL_CATCH_VA_NARGS_IMPL(                                                                \\\n            __VA_ARGS__, INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), \\\n            INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__),              \\\n            INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__),              \\\n            INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__),              \\\n            INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__),              \\\n            INTERNAL_CATCH_NTTP_0)\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...)                                  \\\n    INTERNAL_CATCH_VA_NARGS_IMPL(                                                             \\\n            \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,                    \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)   \\\n    (TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...)                        \\\n    INTERNAL_CATCH_VA_NARGS_IMPL(                                                               \\\n            \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,                     \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)   \\\n    (TestName, ClassName, __VA_ARGS__)\n#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...)                               \\\n    INTERNAL_CATCH_VA_NARGS_IMPL(                                                       \\\n            \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD,                  \\\n            INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD,   \\\n            INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD,   \\\n            INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD,   \\\n            INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD,   \\\n            INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0) \\\n    (TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...)                                             \\\n    INTERNAL_CATCH_VA_NARGS_IMPL(\"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER,           \\\n                                 INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER,   \\\n                                 INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER,   \\\n                                 INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER,   \\\n                                 INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER,   \\\n                                 INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0) \\\n    (TestFunc, __VA_ARGS__)\n#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...)                           \\\n    INTERNAL_CATCH_VA_NARGS_IMPL(                                               \\\n            \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X,             \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)   \\\n    (TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...)                            \\\n    INTERNAL_CATCH_VA_NARGS_IMPL(                                                 \\\n            \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,              \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,  \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)   \\\n    (TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...)                                                      \\\n    INTERNAL_CATCH_VA_NARGS_IMPL(                                                                  \\\n            __VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG, INTERNAL_CATCH_REMOVE_PARENS_10_ARG, \\\n            INTERNAL_CATCH_REMOVE_PARENS_9_ARG, INTERNAL_CATCH_REMOVE_PARENS_8_ARG,                \\\n            INTERNAL_CATCH_REMOVE_PARENS_7_ARG, INTERNAL_CATCH_REMOVE_PARENS_6_ARG,                \\\n            INTERNAL_CATCH_REMOVE_PARENS_5_ARG, INTERNAL_CATCH_REMOVE_PARENS_4_ARG,                \\\n            INTERNAL_CATCH_REMOVE_PARENS_3_ARG, INTERNAL_CATCH_REMOVE_PARENS_2_ARG,                \\\n            INTERNAL_CATCH_REMOVE_PARENS_1_ARG)                                                    \\\n    (__VA_ARGS__)\n#else\n#define INTERNAL_CATCH_NTTP_0(signature)\n#define INTERNAL_CATCH_NTTP_GEN(...)                                                          \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(                                 \\\n            __VA_ARGS__, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, \\\n            INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1,              \\\n            INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1,              \\\n            INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_0)(__VA_ARGS__))\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...)                                  \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(                                 \\\n            \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,                    \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1,                                           \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...)                        \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(                                   \\\n            \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,                     \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1,                                            \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__))\n#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...)                             \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(                         \\\n            \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD,                \\\n            INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, \\\n            INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, \\\n            INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, \\\n            INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, \\\n            INTERNAL_CATCH_NTTP_REGISTER_METHOD0,                                     \\\n            INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...)                                            \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(                                 \\\n            \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, \\\n            INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER,                       \\\n            INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER,                       \\\n            INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER,                       \\\n            INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0,                      \\\n            INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__))\n#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...)                           \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(                   \\\n            \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X,             \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST1,                                    \\\n            INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...)                            \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(                     \\\n            \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,              \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,  \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST1,                                     \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...)                                                      \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(                                      \\\n            __VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG, INTERNAL_CATCH_REMOVE_PARENS_10_ARG, \\\n            INTERNAL_CATCH_REMOVE_PARENS_9_ARG, INTERNAL_CATCH_REMOVE_PARENS_8_ARG,                \\\n            INTERNAL_CATCH_REMOVE_PARENS_7_ARG, INTERNAL_CATCH_REMOVE_PARENS_6_ARG,                \\\n            INTERNAL_CATCH_REMOVE_PARENS_5_ARG, INTERNAL_CATCH_REMOVE_PARENS_4_ARG,                \\\n            INTERNAL_CATCH_REMOVE_PARENS_3_ARG, INTERNAL_CATCH_REMOVE_PARENS_2_ARG,                \\\n            INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__))\n#endif\n\n// end catch_preprocessor.hpp\n// start catch_meta.hpp\n\n#include <type_traits>\n\nnamespace Catch {\ntemplate <typename T>\nstruct always_false : std::false_type {};\n\ntemplate <typename>\nstruct true_given : std::true_type {};\nstruct is_callable_tester {\n    template <typename Fun, typename... Args>\n    true_given<decltype(std::declval<Fun>()(std::declval<Args>()...))> static test(int);\n    template <typename...>\n    std::false_type static test(...);\n};\n\ntemplate <typename T>\nstruct is_callable;\n\ntemplate <typename Fun, typename... Args>\nstruct is_callable<Fun(Args...)> : decltype(is_callable_tester::test<Fun, Args...>(0)) {};\n\n#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703\n// std::result_of is deprecated in C++17 and removed in C++20. Hence, it is\n// replaced with std::invoke_result here.\ntemplate <typename Func, typename... U>\nusing FunctionReturnType =\n        std::remove_reference_t<std::remove_cv_t<std::invoke_result_t<Func, U...>>>;\n#else\n// Keep ::type here because we still support C++11\ntemplate <typename Func, typename... U>\nusing FunctionReturnType = typename std::remove_reference<\n        typename std::remove_cv<typename std::result_of<Func(U...)>::type>::type>::type;\n#endif\n\n}  // namespace Catch\n\nnamespace mpl_ {\nstruct na;\n}\n\n// end catch_meta.hpp\nnamespace Catch {\n\ntemplate <typename C>\nclass TestInvokerAsMethod : public ITestInvoker {\n    void (C::*m_testAsMethod)();\n\npublic:\n    TestInvokerAsMethod(void (C::*testAsMethod)()) noexcept : m_testAsMethod(testAsMethod) {}\n\n    void invoke() const override {\n        C obj;\n        (obj.*m_testAsMethod)();\n    }\n};\n\nauto makeTestInvoker(void (*testAsFunction)()) noexcept -> ITestInvoker *;\n\ntemplate <typename C>\nauto makeTestInvoker(void (C::*testAsMethod)()) noexcept -> ITestInvoker * {\n    return new (std::nothrow) TestInvokerAsMethod<C>(testAsMethod);\n}\n\nstruct NameAndTags {\n    NameAndTags(StringRef const &name_ = StringRef(),\n                StringRef const &tags_ = StringRef()) noexcept;\n    StringRef name;\n    StringRef tags;\n};\n\nstruct AutoReg : NonCopyable {\n    AutoReg(ITestInvoker *invoker,\n            SourceLineInfo const &lineInfo,\n            StringRef const &classOrMethod,\n            NameAndTags const &nameAndTags) noexcept;\n    ~AutoReg();\n};\n\n}  // end namespace Catch\n\n#if defined(CATCH_CONFIG_DISABLE)\n#define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(TestName, ...) static void TestName()\n#define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(TestName, ClassName, ...) \\\n    namespace {                                                                  \\\n    struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) {                  \\\n        void test();                                                             \\\n    };                                                                           \\\n    }                                                                            \\\n    void TestName::test()\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2(TestName, TestFunc, Name, Tags, \\\n                                                            Signature, ...)                 \\\n    INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature))\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2(                      \\\n        TestNameClass, TestName, ClassName, Name, Tags, Signature, ...)                  \\\n    namespace {                                                                          \\\n    namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) {                                  \\\n        INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName,                      \\\n                                               INTERNAL_CATCH_REMOVE_PARENS(Signature)); \\\n    }                                                                                    \\\n    }                                                                                    \\\n    INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature))\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...)               \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2(                                 \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),         \\\n            Name, Tags, typename TestType, __VA_ARGS__)\n#else\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...)               \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2(     \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),         \\\n            Name, Tags, typename TestType, __VA_ARGS__))\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2(                                  \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____),  \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                   \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),          \\\n            Name, Tags, Signature, __VA_ARGS__)\n#else\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2(      \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____),  \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                   \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),          \\\n            Name, Tags, Signature, __VA_ARGS__))\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(ClassName, Name, Tags, ...) \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2(                              \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                      \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____),           \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____),     \\\n            ClassName, Name, Tags, typename T, __VA_ARGS__)\n#else\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(ClassName, Name, Tags, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2(  \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                      \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____),           \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____),     \\\n            ClassName, Name, Tags, typename T, __VA_ARGS__))\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(ClassName, Name, Tags, \\\n                                                                     Signature, ...)        \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2(                             \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                     \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____),          \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____),    \\\n            ClassName, Name, Tags, Signature, __VA_ARGS__)\n#else\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(ClassName, Name, Tags, \\\n                                                                     Signature, ...)        \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                     \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____),          \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____),    \\\n            ClassName, Name, Tags, Signature, __VA_ARGS__))\n#endif\n#endif\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_TESTCASE2(TestName, ...)                                                 \\\n    static void TestName();                                                                     \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                                                   \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                                                    \\\n    namespace {                                                                                 \\\n    Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME(autoRegistrar)(Catch::makeTestInvoker(&TestName), \\\n                                                             CATCH_INTERNAL_LINEINFO,           \\\n                                                             Catch::StringRef(),                \\\n                                                             Catch::NameAndTags{__VA_ARGS__});  \\\n    } /* NOLINT */                                                                              \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                                                    \\\n    static void TestName()\n#define INTERNAL_CATCH_TESTCASE(...) \\\n    INTERNAL_CATCH_TESTCASE2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____), __VA_ARGS__)\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_METHOD_AS_TEST_CASE(QualifiedMethod, ...) \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                    \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                     \\\n    namespace {                                                  \\\n    Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME(autoRegistrar)(    \\\n            Catch::makeTestInvoker(&QualifiedMethod),            \\\n            CATCH_INTERNAL_LINEINFO,                             \\\n            \"&\" #QualifiedMethod,                                \\\n            Catch::NameAndTags{__VA_ARGS__});                    \\\n    } /* NOLINT */                                               \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_TEST_CASE_METHOD2(TestName, ClassName, ...) \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                      \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                       \\\n    namespace {                                                    \\\n    struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) {    \\\n        void test();                                               \\\n    };                                                             \\\n    Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME(autoRegistrar)(      \\\n            Catch::makeTestInvoker(&TestName::test),               \\\n            CATCH_INTERNAL_LINEINFO,                               \\\n            #ClassName,                                            \\\n            Catch::NameAndTags{__VA_ARGS__}); /* NOLINT */         \\\n    }                                                              \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                       \\\n    void TestName::test()\n#define INTERNAL_CATCH_TEST_CASE_METHOD(ClassName, ...)                                        \\\n    INTERNAL_CATCH_TEST_CASE_METHOD2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____), \\\n                                     ClassName, __VA_ARGS__)\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_REGISTER_TESTCASE(Function, ...)                                    \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                                              \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                                               \\\n    Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME(autoRegistrar)(                              \\\n            Catch::makeTestInvoker(Function), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), \\\n            Catch::NameAndTags{__VA_ARGS__}); /* NOLINT */                                 \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(TestName, TestFunc, Name, Tags, Signature, ...)       \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                                                     \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                                                      \\\n    CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS                                                \\\n    CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS                                              \\\n    INTERNAL_CATCH_DECLARE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature));           \\\n    namespace {                                                                                   \\\n    namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) {                                           \\\n        INTERNAL_CATCH_TYPE_GEN                                                                   \\\n        INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))                          \\\n        INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature))            \\\n        template <typename... Types>                                                              \\\n        struct TestName {                                                                         \\\n            TestName() {                                                                          \\\n                int index = 0;                                                                    \\\n                constexpr char const *tmpl_types[] = {                                            \\\n                        CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};    \\\n                using expander = int[];                                                           \\\n                (void)expander{                                                                   \\\n                        (reg_test(Types{},                                                        \\\n                                  Catch::NameAndTags{Name \" - \" + std::string(tmpl_types[index]), \\\n                                                     Tags}),                                      \\\n                         index++)...}; /* NOLINT */                                               \\\n            }                                                                                     \\\n        };                                                                                        \\\n        static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() {                           \\\n            TestName<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(__VA_ARGS__)>();                   \\\n            return 0;                                                                             \\\n        }();                                                                                      \\\n    }                                                                                             \\\n    }                                                                                             \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                                                      \\\n    INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature))\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...)                               \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(                                                 \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),         \\\n            Name, Tags, typename TestType, __VA_ARGS__)\n#else\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...)                               \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(                     \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),         \\\n            Name, Tags, typename TestType, __VA_ARGS__))\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...)                \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(                                                 \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),         \\\n            Name, Tags, Signature, __VA_ARGS__)\n#else\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...)                \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(                     \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),         \\\n            Name, Tags, Signature, __VA_ARGS__))\n#endif\n\n#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(TestName, TestFuncName, Name, Tags, Signature,  \\\n                                                   TmplTypes, TypesList)                           \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                                                      \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                                                       \\\n    CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS                                                 \\\n    CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS                                               \\\n    template <typename TestType>                                                                   \\\n    static void TestFuncName();                                                                    \\\n    namespace {                                                                                    \\\n    namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) {                                            \\\n        INTERNAL_CATCH_TYPE_GEN                                                                    \\\n        INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))                           \\\n        template <typename... Types>                                                               \\\n        struct TestName {                                                                          \\\n            void reg_tests() {                                                                     \\\n                int index = 0;                                                                     \\\n                using expander = int[];                                                            \\\n                constexpr char const *tmpl_types[] = {                                             \\\n                        CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS,                    \\\n                                       INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};                  \\\n                constexpr char const *types_list[] = {                                             \\\n                        CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS,                    \\\n                                       INTERNAL_CATCH_REMOVE_PARENS(TypesList))};                  \\\n                constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);             \\\n                (void)expander{                                                                    \\\n                        (Catch::AutoReg(                                                           \\\n                                 Catch::makeTestInvoker(&TestFuncName<Types>),                     \\\n                                 CATCH_INTERNAL_LINEINFO, Catch::StringRef(),                      \\\n                                 Catch::NameAndTags{                                               \\\n                                         Name \" - \" + std::string(tmpl_types[index / num_types]) + \\\n                                                 \"<\" +                                             \\\n                                                 std::string(types_list[index % num_types]) + \">\", \\\n                                         Tags}),                                                   \\\n                         index++)...}; /* NOLINT */                                                \\\n            }                                                                                      \\\n        };                                                                                         \\\n        static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() {                            \\\n            using TestInit = typename create<                                                      \\\n                    TestName, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()),    \\\n                    TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(                            \\\n                            INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type;                      \\\n            TestInit t;                                                                            \\\n            t.reg_tests();                                                                         \\\n            return 0;                                                                              \\\n        }();                                                                                       \\\n    }                                                                                              \\\n    }                                                                                              \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                                                       \\\n    template <typename TestType>                                                                   \\\n    static void TestFuncName()\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)                       \\\n    INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(                                          \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),         \\\n            Name, Tags, typename T, __VA_ARGS__)\n#else\n#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)                       \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(              \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),         \\\n            Name, Tags, typename T, __VA_ARGS__))\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)        \\\n    INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(                                          \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),         \\\n            Name, Tags, Signature, __VA_ARGS__)\n#else\n#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)        \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(              \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),         \\\n            Name, Tags, Signature, __VA_ARGS__))\n#endif\n\n#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2(TestName, TestFunc, Name, Tags, TmplList)         \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                                                      \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                                                       \\\n    CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS                                               \\\n    template <typename TestType>                                                                   \\\n    static void TestFunc();                                                                        \\\n    namespace {                                                                                    \\\n    namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) {                                            \\\n        INTERNAL_CATCH_TYPE_GEN                                                                    \\\n        template <typename... Types>                                                               \\\n        struct TestName {                                                                          \\\n            void reg_tests() {                                                                     \\\n                int index = 0;                                                                     \\\n                using expander = int[];                                                            \\\n                (void)expander{                                                                    \\\n                        (Catch::AutoReg(                                                           \\\n                                 Catch::makeTestInvoker(&TestFunc<Types>),                         \\\n                                 CATCH_INTERNAL_LINEINFO, Catch::StringRef(),                      \\\n                                 Catch::NameAndTags{                                               \\\n                                         Name \" - \" +                                              \\\n                                                 std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + \\\n                                                 \" - \" + std::to_string(index),                    \\\n                                         Tags}),                                                   \\\n                         index++)...}; /* NOLINT */                                                \\\n            }                                                                                      \\\n        };                                                                                         \\\n        static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() {                            \\\n            using TestInit = typename convert<TestName, TmplList>::type;                           \\\n            TestInit t;                                                                            \\\n            t.reg_tests();                                                                         \\\n            return 0;                                                                              \\\n        }();                                                                                       \\\n    }                                                                                              \\\n    }                                                                                              \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                                                       \\\n    template <typename TestType>                                                                   \\\n    static void TestFunc()\n\n#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(Name, Tags, TmplList)                     \\\n    INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2(                                            \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),         \\\n            Name, Tags, TmplList)\n\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, \\\n                                                   Signature, ...)                                 \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                                                      \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                                                       \\\n    CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS                                                 \\\n    CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS                                               \\\n    namespace {                                                                                    \\\n    namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) {                                            \\\n        INTERNAL_CATCH_TYPE_GEN                                                                    \\\n        INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))                           \\\n        INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName,                                \\\n                                               INTERNAL_CATCH_REMOVE_PARENS(Signature));           \\\n        INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature))      \\\n        template <typename... Types>                                                               \\\n        struct TestNameClass {                                                                     \\\n            TestNameClass() {                                                                      \\\n                int index = 0;                                                                     \\\n                constexpr char const *tmpl_types[] = {                                             \\\n                        CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};     \\\n                using expander = int[];                                                            \\\n                (void)expander{                                                                    \\\n                        (reg_test(Types{}, #ClassName,                                             \\\n                                  Catch::NameAndTags{Name \" - \" + std::string(tmpl_types[index]),  \\\n                                                     Tags}),                                       \\\n                         index++)...}; /* NOLINT */                                                \\\n            }                                                                                      \\\n        };                                                                                         \\\n        static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() {                            \\\n            TestNameClass<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(__VA_ARGS__)>();               \\\n            return 0;                                                                              \\\n        }();                                                                                       \\\n    }                                                                                              \\\n    }                                                                                              \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                                                       \\\n    INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature))\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(ClassName, Name, Tags, ...)             \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(                                          \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____),       \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            ClassName, Name, Tags, typename T, __VA_ARGS__)\n#else\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(ClassName, Name, Tags, ...)             \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(              \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____),       \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            ClassName, Name, Tags, typename T, __VA_ARGS__))\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(ClassName, Name, Tags, Signature, ...) \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(                                             \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                     \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____),          \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____),    \\\n            ClassName, Name, Tags, Signature, __VA_ARGS__)\n#else\n#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(ClassName, Name, Tags, Signature, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(                 \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                     \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____),          \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____),    \\\n            ClassName, Name, Tags, Signature, __VA_ARGS__))\n#endif\n\n#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(                                        \\\n        TestNameClass, TestName, ClassName, Name, Tags, Signature, TmplTypes, TypesList)           \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                                                      \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                                                       \\\n    CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS                                                 \\\n    CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS                                               \\\n    template <typename TestType>                                                                   \\\n    struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName<TestType>) {                          \\\n        void test();                                                                               \\\n    };                                                                                             \\\n    namespace {                                                                                    \\\n    namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestNameClass) {                                       \\\n        INTERNAL_CATCH_TYPE_GEN                                                                    \\\n        INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))                           \\\n        template <typename... Types>                                                               \\\n        struct TestNameClass {                                                                     \\\n            void reg_tests() {                                                                     \\\n                int index = 0;                                                                     \\\n                using expander = int[];                                                            \\\n                constexpr char const *tmpl_types[] = {                                             \\\n                        CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS,                    \\\n                                       INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};                  \\\n                constexpr char const *types_list[] = {                                             \\\n                        CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS,                    \\\n                                       INTERNAL_CATCH_REMOVE_PARENS(TypesList))};                  \\\n                constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);             \\\n                (void)expander{                                                                    \\\n                        (Catch::AutoReg(                                                           \\\n                                 Catch::makeTestInvoker(&TestName<Types>::test),                   \\\n                                 CATCH_INTERNAL_LINEINFO, #ClassName,                              \\\n                                 Catch::NameAndTags{                                               \\\n                                         Name \" - \" + std::string(tmpl_types[index / num_types]) + \\\n                                                 \"<\" +                                             \\\n                                                 std::string(types_list[index % num_types]) + \">\", \\\n                                         Tags}),                                                   \\\n                         index++)...}; /* NOLINT */                                                \\\n            }                                                                                      \\\n        };                                                                                         \\\n        static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() {                            \\\n            using TestInit = typename create<                                                      \\\n                    TestNameClass,                                                                 \\\n                    decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()),              \\\n                    TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(                            \\\n                            INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type;                      \\\n            TestInit t;                                                                            \\\n            t.reg_tests();                                                                         \\\n            return 0;                                                                              \\\n        }();                                                                                       \\\n    }                                                                                              \\\n    }                                                                                              \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                                                       \\\n    template <typename TestType>                                                                   \\\n    void TestName<TestType>::test()\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(ClassName, Name, Tags, ...)     \\\n    INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(                                  \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),         \\\n            ClassName, Name, Tags, typename T, __VA_ARGS__)\n#else\n#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(ClassName, Name, Tags, ...)     \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(      \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),         \\\n            ClassName, Name, Tags, typename T, __VA_ARGS__))\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(ClassName, Name, Tags, Signature, \\\n                                                             ...)                              \\\n    INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(                                        \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____),       \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                        \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),               \\\n            ClassName, Name, Tags, Signature, __VA_ARGS__)\n#else\n#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(ClassName, Name, Tags, Signature, \\\n                                                             ...)                              \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(            \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____),       \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                        \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),               \\\n            ClassName, Name, Tags, Signature, __VA_ARGS__))\n#endif\n\n#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name,  \\\n                                                        Tags, TmplList)                            \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                                                      \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                                                       \\\n    CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS                                               \\\n    template <typename TestType>                                                                   \\\n    struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName<TestType>) {                          \\\n        void test();                                                                               \\\n    };                                                                                             \\\n    namespace {                                                                                    \\\n    namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) {                                            \\\n        INTERNAL_CATCH_TYPE_GEN                                                                    \\\n        template <typename... Types>                                                               \\\n        struct TestNameClass {                                                                     \\\n            void reg_tests() {                                                                     \\\n                int index = 0;                                                                     \\\n                using expander = int[];                                                            \\\n                (void)expander{                                                                    \\\n                        (Catch::AutoReg(                                                           \\\n                                 Catch::makeTestInvoker(&TestName<Types>::test),                   \\\n                                 CATCH_INTERNAL_LINEINFO, #ClassName,                              \\\n                                 Catch::NameAndTags{                                               \\\n                                         Name \" - \" +                                              \\\n                                                 std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + \\\n                                                 \" - \" + std::to_string(index),                    \\\n                                         Tags}),                                                   \\\n                         index++)...}; /* NOLINT */                                                \\\n            }                                                                                      \\\n        };                                                                                         \\\n        static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() {                            \\\n            using TestInit = typename convert<TestNameClass, TmplList>::type;                      \\\n            TestInit t;                                                                            \\\n            t.reg_tests();                                                                         \\\n            return 0;                                                                              \\\n        }();                                                                                       \\\n    }                                                                                              \\\n    }                                                                                              \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                                                       \\\n    template <typename TestType>                                                                   \\\n    void TestName<TestType>::test()\n\n#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(ClassName, Name, Tags, TmplList)   \\\n    INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2(                                     \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \\\n            INTERNAL_CATCH_UNIQUE_NAME(                                                  \\\n                    ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____),         \\\n            ClassName, Name, Tags, TmplList)\n\n// end catch_test_registry.h\n// start catch_capture.hpp\n\n// start catch_assertionhandler.h\n\n// start catch_assertioninfo.h\n\n// start catch_result_type.h\n\nnamespace Catch {\n\n// ResultWas::OfType enum\nstruct ResultWas {\n    enum OfType {\n        Unknown = -1,\n        Ok = 0,\n        Info = 1,\n        Warning = 2,\n\n        FailureBit = 0x10,\n\n        ExpressionFailed = FailureBit | 1,\n        ExplicitFailure = FailureBit | 2,\n\n        Exception = 0x100 | FailureBit,\n\n        ThrewException = Exception | 1,\n        DidntThrowException = Exception | 2,\n\n        FatalErrorCondition = 0x200 | FailureBit\n\n    };\n};\n\nbool isOk(ResultWas::OfType resultType);\nbool isJustInfo(int flags);\n\n// ResultDisposition::Flags enum\nstruct ResultDisposition {\n    enum Flags {\n        Normal = 0x01,\n\n        ContinueOnFailure = 0x02,  // Failures fail test, but execution continues\n        FalseTest = 0x04,          // Prefix expression with !\n        SuppressFail = 0x08        // Failures are reported but do not fail the test\n    };\n};\n\nResultDisposition::Flags operator|(ResultDisposition::Flags lhs, ResultDisposition::Flags rhs);\n\nbool shouldContinueOnFailure(int flags);\ninline bool isFalseTest(int flags) { return (flags & ResultDisposition::FalseTest) != 0; }\nbool shouldSuppressFailure(int flags);\n\n}  // end namespace Catch\n\n// end catch_result_type.h\nnamespace Catch {\n\nstruct AssertionInfo {\n    StringRef macroName;\n    SourceLineInfo lineInfo;\n    StringRef capturedExpression;\n    ResultDisposition::Flags resultDisposition;\n\n    // We want to delete this constructor but a compiler bug in 4.8 means\n    // the struct is then treated as non-aggregate\n    // AssertionInfo() = delete;\n};\n\n}  // end namespace Catch\n\n// end catch_assertioninfo.h\n// start catch_decomposer.h\n\n// start catch_tostring.h\n\n#include <cstddef>\n#include <string>\n#include <type_traits>\n#include <vector>\n// start catch_stream.h\n\n#include <cstddef>\n#include <iosfwd>\n#include <ostream>\n\nnamespace Catch {\n\nstd::ostream &cout();\nstd::ostream &cerr();\nstd::ostream &clog();\n\nclass StringRef;\n\nstruct IStream {\n    virtual ~IStream();\n    virtual std::ostream &stream() const = 0;\n};\n\nauto makeStream(StringRef const &filename) -> IStream const *;\n\nclass ReusableStringStream : NonCopyable {\n    std::size_t m_index;\n    std::ostream *m_oss;\n\npublic:\n    ReusableStringStream();\n    ~ReusableStringStream();\n\n    auto str() const -> std::string;\n\n    template <typename T>\n    auto operator<<(T const &value) -> ReusableStringStream & {\n        *m_oss << value;\n        return *this;\n    }\n    auto get() -> std::ostream & { return *m_oss; }\n};\n}  // namespace Catch\n\n// end catch_stream.h\n// start catch_interfaces_enum_values_registry.h\n\n#include <vector>\n\nnamespace Catch {\n\nnamespace Detail {\nstruct EnumInfo {\n    StringRef m_name;\n    std::vector<std::pair<int, StringRef>> m_values;\n\n    ~EnumInfo();\n\n    StringRef lookup(int value) const;\n};\n}  // namespace Detail\n\nstruct IMutableEnumValuesRegistry {\n    virtual ~IMutableEnumValuesRegistry();\n\n    virtual Detail::EnumInfo const &registerEnum(StringRef enumName,\n                                                 StringRef allEnums,\n                                                 std::vector<int> const &values) = 0;\n\n    template <typename E>\n    Detail::EnumInfo const &registerEnum(StringRef enumName,\n                                         StringRef allEnums,\n                                         std::initializer_list<E> values) {\n        static_assert(sizeof(int) >= sizeof(E), \"Cannot serialize enum to int\");\n        std::vector<int> intValues;\n        intValues.reserve(values.size());\n        for (auto enumValue : values)\n            intValues.push_back(static_cast<int>(enumValue));\n        return registerEnum(enumName, allEnums, intValues);\n    }\n};\n\n}  // namespace Catch\n\n// end catch_interfaces_enum_values_registry.h\n\n#ifdef CATCH_CONFIG_CPP17_STRING_VIEW\n#include <string_view>\n#endif\n\n#ifdef __OBJC__\n// start catch_objc_arc.hpp\n\n#import <Foundation/Foundation.h>\n\n#ifdef __has_feature\n#define CATCH_ARC_ENABLED __has_feature(objc_arc)\n#else\n#define CATCH_ARC_ENABLED 0\n#endif\n\nvoid arcSafeRelease(NSObject *obj);\nid performOptionalSelector(id obj, SEL sel);\n\n#if !CATCH_ARC_ENABLED\ninline void arcSafeRelease(NSObject *obj) { [obj release]; }\ninline id performOptionalSelector(id obj, SEL sel) {\n    if ([obj respondsToSelector:sel])\n        return [obj performSelector:sel];\n    return nil;\n}\n#define CATCH_UNSAFE_UNRETAINED\n#define CATCH_ARC_STRONG\n#else\ninline void arcSafeRelease(NSObject *) {}\ninline id performOptionalSelector(id obj, SEL sel) {\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Warc-performSelector-leaks\"\n#endif\n    if ([obj respondsToSelector:sel])\n        return [obj performSelector:sel];\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n    return nil;\n}\n#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained\n#define CATCH_ARC_STRONG __strong\n#endif\n\n// end catch_objc_arc.hpp\n#endif\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable : 4180)  // We attempt to stream a function (address) by const&, \\\n                                 // which MSVC complains about but is harmless\n#endif\n\nnamespace Catch {\nnamespace Detail {\n\nextern const std::string unprintableString;\n\nstd::string rawMemoryToString(const void *object, std::size_t size);\n\ntemplate <typename T>\nstd::string rawMemoryToString(const T &object) {\n    return rawMemoryToString(&object, sizeof(object));\n}\n\ntemplate <typename T>\nclass IsStreamInsertable {\n    template <typename Stream, typename U>\n    static auto test(int)\n            -> decltype(std::declval<Stream &>() << std::declval<U>(), std::true_type());\n\n    template <typename, typename>\n    static auto test(...) -> std::false_type;\n\npublic:\n    static const bool value = decltype(test<std::ostream, const T &>(0))::value;\n};\n\ntemplate <typename E>\nstd::string convertUnknownEnumToString(E e);\n\ntemplate <typename T>\ntypename std::enable_if<!std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value,\n                        std::string>::type\nconvertUnstreamable(T const &) {\n    return Detail::unprintableString;\n}\ntemplate <typename T>\ntypename std::enable_if<!std::is_enum<T>::value && std::is_base_of<std::exception, T>::value,\n                        std::string>::type\nconvertUnstreamable(T const &ex) {\n    return ex.what();\n}\n\ntemplate <typename T>\ntypename std::enable_if<std::is_enum<T>::value, std::string>::type convertUnstreamable(\n        T const &value) {\n    return convertUnknownEnumToString(value);\n}\n\n#if defined(_MANAGED)\n//! Convert a CLR string to a utf8 std::string\ntemplate <typename T>\nstd::string clrReferenceToString(T ^ ref) {\n    if (ref == nullptr)\n        return std::string(\"null\");\n    auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString());\n    cli::pin_ptr<System::Byte> p = &bytes[0];\n    return std::string(reinterpret_cast<char const *>(p), bytes->Length);\n}\n#endif\n\n}  // namespace Detail\n\n// If we decide for C++14, change these to enable_if_ts\ntemplate <typename T, typename = void>\nstruct StringMaker {\n    template <typename Fake = T>\n    static typename std::enable_if<::Catch::Detail::IsStreamInsertable<Fake>::value,\n                                   std::string>::type\n    convert(const Fake &value) {\n        ReusableStringStream rss;\n        // NB: call using the function-like syntax to avoid ambiguity with\n        // user-defined templated operator<< under clang.\n        rss.operator<<(value);\n        return rss.str();\n    }\n\n    template <typename Fake = T>\n    static typename std::enable_if<!::Catch::Detail::IsStreamInsertable<Fake>::value,\n                                   std::string>::type\n    convert(const Fake &value) {\n#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER)\n        return Detail::convertUnstreamable(value);\n#else\n        return CATCH_CONFIG_FALLBACK_STRINGIFIER(value);\n#endif\n    }\n};\n\nnamespace Detail {\n\n// This function dispatches all stringification requests inside of Catch.\n// Should be preferably called fully qualified, like ::Catch::Detail::stringify\ntemplate <typename T>\nstd::string stringify(const T &e) {\n    return ::Catch::StringMaker<\n            typename std::remove_cv<typename std::remove_reference<T>::type>::type>::convert(e);\n}\n\ntemplate <typename E>\nstd::string convertUnknownEnumToString(E e) {\n    return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<E>::type>(e));\n}\n\n#if defined(_MANAGED)\ntemplate <typename T>\nstd::string stringify(T ^ e) {\n    return ::Catch::StringMaker<T ^>::convert(e);\n}\n#endif\n\n}  // namespace Detail\n\n// Some predefined specializations\n\ntemplate <>\nstruct StringMaker<std::string> {\n    static std::string convert(const std::string &str);\n};\n\n#ifdef CATCH_CONFIG_CPP17_STRING_VIEW\ntemplate <>\nstruct StringMaker<std::string_view> {\n    static std::string convert(std::string_view str);\n};\n#endif\n\ntemplate <>\nstruct StringMaker<char const *> {\n    static std::string convert(char const *str);\n};\ntemplate <>\nstruct StringMaker<char *> {\n    static std::string convert(char *str);\n};\n\n#ifdef CATCH_CONFIG_WCHAR\ntemplate <>\nstruct StringMaker<std::wstring> {\n    static std::string convert(const std::wstring &wstr);\n};\n\n#ifdef CATCH_CONFIG_CPP17_STRING_VIEW\ntemplate <>\nstruct StringMaker<std::wstring_view> {\n    static std::string convert(std::wstring_view str);\n};\n#endif\n\ntemplate <>\nstruct StringMaker<wchar_t const *> {\n    static std::string convert(wchar_t const *str);\n};\ntemplate <>\nstruct StringMaker<wchar_t *> {\n    static std::string convert(wchar_t *str);\n};\n#endif\n\n// TBD: Should we use `strnlen` to ensure that we don't go out of the buffer,\n//      while keeping string semantics?\ntemplate <int SZ>\nstruct StringMaker<char[SZ]> {\n    static std::string convert(char const *str) {\n        return ::Catch::Detail::stringify(std::string{str});\n    }\n};\ntemplate <int SZ>\nstruct StringMaker<signed char[SZ]> {\n    static std::string convert(signed char const *str) {\n        return ::Catch::Detail::stringify(std::string{reinterpret_cast<char const *>(str)});\n    }\n};\ntemplate <int SZ>\nstruct StringMaker<unsigned char[SZ]> {\n    static std::string convert(unsigned char const *str) {\n        return ::Catch::Detail::stringify(std::string{reinterpret_cast<char const *>(str)});\n    }\n};\n\n#if defined(CATCH_CONFIG_CPP17_BYTE)\ntemplate <>\nstruct StringMaker<std::byte> {\n    static std::string convert(std::byte value);\n};\n#endif  // defined(CATCH_CONFIG_CPP17_BYTE)\ntemplate <>\nstruct StringMaker<int> {\n    static std::string convert(int value);\n};\ntemplate <>\nstruct StringMaker<long> {\n    static std::string convert(long value);\n};\ntemplate <>\nstruct StringMaker<long long> {\n    static std::string convert(long long value);\n};\ntemplate <>\nstruct StringMaker<unsigned int> {\n    static std::string convert(unsigned int value);\n};\ntemplate <>\nstruct StringMaker<unsigned long> {\n    static std::string convert(unsigned long value);\n};\ntemplate <>\nstruct StringMaker<unsigned long long> {\n    static std::string convert(unsigned long long value);\n};\n\ntemplate <>\nstruct StringMaker<bool> {\n    static std::string convert(bool b);\n};\n\ntemplate <>\nstruct StringMaker<char> {\n    static std::string convert(char c);\n};\ntemplate <>\nstruct StringMaker<signed char> {\n    static std::string convert(signed char c);\n};\ntemplate <>\nstruct StringMaker<unsigned char> {\n    static std::string convert(unsigned char c);\n};\n\ntemplate <>\nstruct StringMaker<std::nullptr_t> {\n    static std::string convert(std::nullptr_t);\n};\n\ntemplate <>\nstruct StringMaker<float> {\n    static std::string convert(float value);\n    static int precision;\n};\n\ntemplate <>\nstruct StringMaker<double> {\n    static std::string convert(double value);\n    static int precision;\n};\n\ntemplate <typename T>\nstruct StringMaker<T *> {\n    template <typename U>\n    static std::string convert(U *p) {\n        if (p) {\n            return ::Catch::Detail::rawMemoryToString(p);\n        } else {\n            return \"nullptr\";\n        }\n    }\n};\n\ntemplate <typename R, typename C>\nstruct StringMaker<R C::*> {\n    static std::string convert(R C::*p) {\n        if (p) {\n            return ::Catch::Detail::rawMemoryToString(p);\n        } else {\n            return \"nullptr\";\n        }\n    }\n};\n\n#if defined(_MANAGED)\ntemplate <typename T>\nstruct StringMaker<T ^> {\n    static std::string convert(T ^ ref) { return ::Catch::Detail::clrReferenceToString(ref); }\n};\n#endif\n\nnamespace Detail {\ntemplate <typename InputIterator, typename Sentinel = InputIterator>\nstd::string rangeToString(InputIterator first, Sentinel last) {\n    ReusableStringStream rss;\n    rss << \"{ \";\n    if (first != last) {\n        rss << ::Catch::Detail::stringify(*first);\n        for (++first; first != last; ++first)\n            rss << \", \" << ::Catch::Detail::stringify(*first);\n    }\n    rss << \" }\";\n    return rss.str();\n}\n}  // namespace Detail\n\n#ifdef __OBJC__\ntemplate <>\nstruct StringMaker<NSString *> {\n    static std::string convert(NSString *nsstring) {\n        if (!nsstring)\n            return \"nil\";\n        return std::string(\"@\") + [nsstring UTF8String];\n    }\n};\ntemplate <>\nstruct StringMaker<NSObject *> {\n    static std::string convert(NSObject *nsObject) {\n        return ::Catch::Detail::stringify([nsObject description]);\n    }\n};\nnamespace Detail {\ninline std::string stringify(NSString *nsstring) {\n    return StringMaker<NSString *>::convert(nsstring);\n}\n\n}  // namespace Detail\n#endif  // __OBJC__\n\n}  // namespace Catch\n\n//////////////////////////////////////////////////////\n// Separate std-lib types stringification, so it can be selectively enabled\n// This means that we do not bring in\n\n#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS)\n#define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER\n#define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER\n#define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER\n#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER\n#define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER\n#endif\n\n// Separate std::pair specialization\n#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER)\n#include <utility>\nnamespace Catch {\ntemplate <typename T1, typename T2>\nstruct StringMaker<std::pair<T1, T2>> {\n    static std::string convert(const std::pair<T1, T2> &pair) {\n        ReusableStringStream rss;\n        rss << \"{ \" << ::Catch::Detail::stringify(pair.first) << \", \"\n            << ::Catch::Detail::stringify(pair.second) << \" }\";\n        return rss.str();\n    }\n};\n}  // namespace Catch\n#endif  // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER\n\n#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL)\n#include <optional>\nnamespace Catch {\ntemplate <typename T>\nstruct StringMaker<std::optional<T>> {\n    static std::string convert(const std::optional<T> &optional) {\n        ReusableStringStream rss;\n        if (optional.has_value()) {\n            rss << ::Catch::Detail::stringify(*optional);\n        } else {\n            rss << \"{ }\";\n        }\n        return rss.str();\n    }\n};\n}  // namespace Catch\n#endif  // CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER\n\n// Separate std::tuple specialization\n#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER)\n#include <tuple>\nnamespace Catch {\nnamespace Detail {\ntemplate <typename Tuple, std::size_t N = 0, bool = (N < std::tuple_size<Tuple>::value)>\nstruct TupleElementPrinter {\n    static void print(const Tuple &tuple, std::ostream &os) {\n        os << (N ? \", \" : \" \") << ::Catch::Detail::stringify(std::get<N>(tuple));\n        TupleElementPrinter<Tuple, N + 1>::print(tuple, os);\n    }\n};\n\ntemplate <typename Tuple, std::size_t N>\nstruct TupleElementPrinter<Tuple, N, false> {\n    static void print(const Tuple &, std::ostream &) {}\n};\n\n}  // namespace Detail\n\ntemplate <typename... Types>\nstruct StringMaker<std::tuple<Types...>> {\n    static std::string convert(const std::tuple<Types...> &tuple) {\n        ReusableStringStream rss;\n        rss << '{';\n        Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get());\n        rss << \" }\";\n        return rss.str();\n    }\n};\n}  // namespace Catch\n#endif  // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER\n\n#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT)\n#include <variant>\nnamespace Catch {\ntemplate <>\nstruct StringMaker<std::monostate> {\n    static std::string convert(const std::monostate &) { return \"{ }\"; }\n};\n\ntemplate <typename... Elements>\nstruct StringMaker<std::variant<Elements...>> {\n    static std::string convert(const std::variant<Elements...> &variant) {\n        if (variant.valueless_by_exception()) {\n            return \"{valueless variant}\";\n        } else {\n            return std::visit([](const auto &value) { return ::Catch::Detail::stringify(value); },\n                              variant);\n        }\n    }\n};\n}  // namespace Catch\n#endif  // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER\n\nnamespace Catch {\n// Import begin/ end from std here\nusing std::begin;\nusing std::end;\n\nnamespace detail {\ntemplate <typename...>\nstruct void_type {\n    using type = void;\n};\n\ntemplate <typename T, typename = void>\nstruct is_range_impl : std::false_type {};\n\ntemplate <typename T>\nstruct is_range_impl<T, typename void_type<decltype(begin(std::declval<T>()))>::type>\n        : std::true_type {};\n}  // namespace detail\n\ntemplate <typename T>\nstruct is_range : detail::is_range_impl<T> {};\n\n#if defined(_MANAGED)  // Managed types are never ranges\ntemplate <typename T>\nstruct is_range<T ^> {\n    static const bool value = false;\n};\n#endif\n\ntemplate <typename Range>\nstd::string rangeToString(Range const &range) {\n    return ::Catch::Detail::rangeToString(begin(range), end(range));\n}\n\n// Handle vector<bool> specially\ntemplate <typename Allocator>\nstd::string rangeToString(std::vector<bool, Allocator> const &v) {\n    ReusableStringStream rss;\n    rss << \"{ \";\n    bool first = true;\n    for (bool b : v) {\n        if (first)\n            first = false;\n        else\n            rss << \", \";\n        rss << ::Catch::Detail::stringify(b);\n    }\n    rss << \" }\";\n    return rss.str();\n}\n\ntemplate <typename R>\nstruct StringMaker<R,\n                   typename std::enable_if<is_range<R>::value &&\n                                           !::Catch::Detail::IsStreamInsertable<R>::value>::type> {\n    static std::string convert(R const &range) { return rangeToString(range); }\n};\n\ntemplate <typename T, int SZ>\nstruct StringMaker<T[SZ]> {\n    static std::string convert(T const (&arr)[SZ]) { return rangeToString(arr); }\n};\n\n}  // namespace Catch\n\n// Separate std::chrono::duration specialization\n#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)\n#include <chrono>\n#include <ctime>\n#include <ratio>\n\nnamespace Catch {\n\ntemplate <class Ratio>\nstruct ratio_string {\n    static std::string symbol();\n};\n\ntemplate <class Ratio>\nstd::string ratio_string<Ratio>::symbol() {\n    Catch::ReusableStringStream rss;\n    rss << '[' << Ratio::num << '/' << Ratio::den << ']';\n    return rss.str();\n}\ntemplate <>\nstruct ratio_string<std::atto> {\n    static std::string symbol();\n};\ntemplate <>\nstruct ratio_string<std::femto> {\n    static std::string symbol();\n};\ntemplate <>\nstruct ratio_string<std::pico> {\n    static std::string symbol();\n};\ntemplate <>\nstruct ratio_string<std::nano> {\n    static std::string symbol();\n};\ntemplate <>\nstruct ratio_string<std::micro> {\n    static std::string symbol();\n};\ntemplate <>\nstruct ratio_string<std::milli> {\n    static std::string symbol();\n};\n\n////////////\n// std::chrono::duration specializations\ntemplate <typename Value, typename Ratio>\nstruct StringMaker<std::chrono::duration<Value, Ratio>> {\n    static std::string convert(std::chrono::duration<Value, Ratio> const &duration) {\n        ReusableStringStream rss;\n        rss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's';\n        return rss.str();\n    }\n};\ntemplate <typename Value>\nstruct StringMaker<std::chrono::duration<Value, std::ratio<1>>> {\n    static std::string convert(std::chrono::duration<Value, std::ratio<1>> const &duration) {\n        ReusableStringStream rss;\n        rss << duration.count() << \" s\";\n        return rss.str();\n    }\n};\ntemplate <typename Value>\nstruct StringMaker<std::chrono::duration<Value, std::ratio<60>>> {\n    static std::string convert(std::chrono::duration<Value, std::ratio<60>> const &duration) {\n        ReusableStringStream rss;\n        rss << duration.count() << \" m\";\n        return rss.str();\n    }\n};\ntemplate <typename Value>\nstruct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> {\n    static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const &duration) {\n        ReusableStringStream rss;\n        rss << duration.count() << \" h\";\n        return rss.str();\n    }\n};\n\n////////////\n// std::chrono::time_point specialization\n// Generic time_point cannot be specialized, only\n// std::chrono::time_point<system_clock>\ntemplate <typename Clock, typename Duration>\nstruct StringMaker<std::chrono::time_point<Clock, Duration>> {\n    static std::string convert(std::chrono::time_point<Clock, Duration> const &time_point) {\n        return ::Catch::Detail::stringify(time_point.time_since_epoch()) + \" since epoch\";\n    }\n};\n// std::chrono::time_point<system_clock> specialization\ntemplate <typename Duration>\nstruct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> {\n    static std::string convert(\n            std::chrono::time_point<std::chrono::system_clock, Duration> const &time_point) {\n        auto converted = std::chrono::system_clock::to_time_t(time_point);\n\n#ifdef _MSC_VER\n        std::tm timeInfo = {};\n        gmtime_s(&timeInfo, &converted);\n#else\n        std::tm *timeInfo = std::gmtime(&converted);\n#endif\n\n        auto const timeStampSize = sizeof(\"2017-01-16T17:06:45Z\");\n        char timeStamp[timeStampSize];\n        const char *const fmt = \"%Y-%m-%dT%H:%M:%SZ\";\n\n#ifdef _MSC_VER\n        std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);\n#else\n        std::strftime(timeStamp, timeStampSize, fmt, timeInfo);\n#endif\n        return std::string(timeStamp);\n    }\n};\n}  // namespace Catch\n#endif  // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER\n\n#define INTERNAL_CATCH_REGISTER_ENUM(enumName, ...)                                               \\\n    namespace Catch {                                                                             \\\n    template <>                                                                                   \\\n    struct StringMaker<enumName> {                                                                \\\n        static std::string convert(enumName value) {                                              \\\n            static const auto &enumInfo =                                                         \\\n                    ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( \\\n                            #enumName, #__VA_ARGS__, {__VA_ARGS__});                              \\\n            return static_cast<std::string>(enumInfo.lookup(static_cast<int>(value)));            \\\n        }                                                                                         \\\n    };                                                                                            \\\n    }\n\n#define CATCH_REGISTER_ENUM(enumName, ...) INTERNAL_CATCH_REGISTER_ENUM(enumName, __VA_ARGS__)\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n// end catch_tostring.h\n#include <iosfwd>\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable : 4389)  // '==' : signed/unsigned mismatch\n#pragma warning(disable : 4018)  // more \"signed/unsigned mismatch\"\n#pragma warning(disable : 4312)  // Converting int to T* using reinterpret_cast \\\n                                 // (issue on x64 platform)\n#pragma warning(disable : 4180)  // qualifier applied to function type has no meaning\n#pragma warning(disable : 4800)  // Forcing result to true or false\n#endif\n\nnamespace Catch {\n\nstruct ITransientExpression {\n    auto isBinaryExpression() const -> bool { return m_isBinaryExpression; }\n    auto getResult() const -> bool { return m_result; }\n    virtual void streamReconstructedExpression(std::ostream &os) const = 0;\n\n    ITransientExpression(bool isBinaryExpression, bool result)\n            : m_isBinaryExpression(isBinaryExpression), m_result(result) {}\n\n    // We don't actually need a virtual destructor, but many static analysers\n    // complain if it's not here :-(\n    virtual ~ITransientExpression();\n\n    bool m_isBinaryExpression;\n    bool m_result;\n};\n\nvoid formatReconstructedExpression(std::ostream &os,\n                                   std::string const &lhs,\n                                   StringRef op,\n                                   std::string const &rhs);\n\ntemplate <typename LhsT, typename RhsT>\nclass BinaryExpr : public ITransientExpression {\n    LhsT m_lhs;\n    StringRef m_op;\n    RhsT m_rhs;\n\n    void streamReconstructedExpression(std::ostream &os) const override {\n        formatReconstructedExpression(os, Catch::Detail::stringify(m_lhs), m_op,\n                                      Catch::Detail::stringify(m_rhs));\n    }\n\npublic:\n    BinaryExpr(bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs)\n            : ITransientExpression{true, comparisonResult}, m_lhs(lhs), m_op(op), m_rhs(rhs) {}\n\n    template <typename T>\n    auto operator&&(T) const -> BinaryExpr<LhsT, RhsT const &> const {\n        static_assert(always_false<T>::value,\n                      \"chained comparisons are not supported inside assertions, \"\n                      \"wrap the expression inside parentheses, or decompose it\");\n    }\n\n    template <typename T>\n    auto operator||(T) const -> BinaryExpr<LhsT, RhsT const &> const {\n        static_assert(always_false<T>::value,\n                      \"chained comparisons are not supported inside assertions, \"\n                      \"wrap the expression inside parentheses, or decompose it\");\n    }\n\n    template <typename T>\n    auto operator==(T) const -> BinaryExpr<LhsT, RhsT const &> const {\n        static_assert(always_false<T>::value,\n                      \"chained comparisons are not supported inside assertions, \"\n                      \"wrap the expression inside parentheses, or decompose it\");\n    }\n\n    template <typename T>\n    auto operator!=(T) const -> BinaryExpr<LhsT, RhsT const &> const {\n        static_assert(always_false<T>::value,\n                      \"chained comparisons are not supported inside assertions, \"\n                      \"wrap the expression inside parentheses, or decompose it\");\n    }\n\n    template <typename T>\n    auto operator>(T) const -> BinaryExpr<LhsT, RhsT const &> const {\n        static_assert(always_false<T>::value,\n                      \"chained comparisons are not supported inside assertions, \"\n                      \"wrap the expression inside parentheses, or decompose it\");\n    }\n\n    template <typename T>\n    auto operator<(T) const -> BinaryExpr<LhsT, RhsT const &> const {\n        static_assert(always_false<T>::value,\n                      \"chained comparisons are not supported inside assertions, \"\n                      \"wrap the expression inside parentheses, or decompose it\");\n    }\n\n    template <typename T>\n    auto operator>=(T) const -> BinaryExpr<LhsT, RhsT const &> const {\n        static_assert(always_false<T>::value,\n                      \"chained comparisons are not supported inside assertions, \"\n                      \"wrap the expression inside parentheses, or decompose it\");\n    }\n\n    template <typename T>\n    auto operator<=(T) const -> BinaryExpr<LhsT, RhsT const &> const {\n        static_assert(always_false<T>::value,\n                      \"chained comparisons are not supported inside assertions, \"\n                      \"wrap the expression inside parentheses, or decompose it\");\n    }\n};\n\ntemplate <typename LhsT>\nclass UnaryExpr : public ITransientExpression {\n    LhsT m_lhs;\n\n    void streamReconstructedExpression(std::ostream &os) const override {\n        os << Catch::Detail::stringify(m_lhs);\n    }\n\npublic:\n    explicit UnaryExpr(LhsT lhs)\n            : ITransientExpression{false, static_cast<bool>(lhs)}, m_lhs(lhs) {}\n};\n\n// Specialised comparison functions to handle equality comparisons between ints\n// and pointers (NULL deduces as an int)\ntemplate <typename LhsT, typename RhsT>\nauto compareEqual(LhsT const &lhs, RhsT const &rhs) -> bool {\n    return static_cast<bool>(lhs == rhs);\n}\ntemplate <typename T>\nauto compareEqual(T *const &lhs, int rhs) -> bool {\n    return lhs == reinterpret_cast<void const *>(rhs);\n}\ntemplate <typename T>\nauto compareEqual(T *const &lhs, long rhs) -> bool {\n    return lhs == reinterpret_cast<void const *>(rhs);\n}\ntemplate <typename T>\nauto compareEqual(int lhs, T *const &rhs) -> bool {\n    return reinterpret_cast<void const *>(lhs) == rhs;\n}\ntemplate <typename T>\nauto compareEqual(long lhs, T *const &rhs) -> bool {\n    return reinterpret_cast<void const *>(lhs) == rhs;\n}\n\ntemplate <typename LhsT, typename RhsT>\nauto compareNotEqual(LhsT const &lhs, RhsT &&rhs) -> bool {\n    return static_cast<bool>(lhs != rhs);\n}\ntemplate <typename T>\nauto compareNotEqual(T *const &lhs, int rhs) -> bool {\n    return lhs != reinterpret_cast<void const *>(rhs);\n}\ntemplate <typename T>\nauto compareNotEqual(T *const &lhs, long rhs) -> bool {\n    return lhs != reinterpret_cast<void const *>(rhs);\n}\ntemplate <typename T>\nauto compareNotEqual(int lhs, T *const &rhs) -> bool {\n    return reinterpret_cast<void const *>(lhs) != rhs;\n}\ntemplate <typename T>\nauto compareNotEqual(long lhs, T *const &rhs) -> bool {\n    return reinterpret_cast<void const *>(lhs) != rhs;\n}\n\ntemplate <typename LhsT>\nclass ExprLhs {\n    LhsT m_lhs;\n\npublic:\n    explicit ExprLhs(LhsT lhs) : m_lhs(lhs) {}\n\n    template <typename RhsT>\n    auto operator==(RhsT const &rhs) -> BinaryExpr<LhsT, RhsT const &> const {\n        return {compareEqual(m_lhs, rhs), m_lhs, \"==\", rhs};\n    }\n    auto operator==(bool rhs) -> BinaryExpr<LhsT, bool> const {\n        return {m_lhs == rhs, m_lhs, \"==\", rhs};\n    }\n\n    template <typename RhsT>\n    auto operator!=(RhsT const &rhs) -> BinaryExpr<LhsT, RhsT const &> const {\n        return {compareNotEqual(m_lhs, rhs), m_lhs, \"!=\", rhs};\n    }\n    auto operator!=(bool rhs) -> BinaryExpr<LhsT, bool> const {\n        return {m_lhs != rhs, m_lhs, \"!=\", rhs};\n    }\n\n    template <typename RhsT>\n    auto operator>(RhsT const &rhs) -> BinaryExpr<LhsT, RhsT const &> const {\n        return {static_cast<bool>(m_lhs > rhs), m_lhs, \">\", rhs};\n    }\n    template <typename RhsT>\n    auto operator<(RhsT const &rhs) -> BinaryExpr<LhsT, RhsT const &> const {\n        return {static_cast<bool>(m_lhs < rhs), m_lhs, \"<\", rhs};\n    }\n    template <typename RhsT>\n    auto operator>=(RhsT const &rhs) -> BinaryExpr<LhsT, RhsT const &> const {\n        return {static_cast<bool>(m_lhs >= rhs), m_lhs, \">=\", rhs};\n    }\n    template <typename RhsT>\n    auto operator<=(RhsT const &rhs) -> BinaryExpr<LhsT, RhsT const &> const {\n        return {static_cast<bool>(m_lhs <= rhs), m_lhs, \"<=\", rhs};\n    }\n    template <typename RhsT>\n    auto operator|(RhsT const &rhs) -> BinaryExpr<LhsT, RhsT const &> const {\n        return {static_cast<bool>(m_lhs | rhs), m_lhs, \"|\", rhs};\n    }\n    template <typename RhsT>\n    auto operator&(RhsT const &rhs) -> BinaryExpr<LhsT, RhsT const &> const {\n        return {static_cast<bool>(m_lhs & rhs), m_lhs, \"&\", rhs};\n    }\n    template <typename RhsT>\n    auto operator^(RhsT const &rhs) -> BinaryExpr<LhsT, RhsT const &> const {\n        return {static_cast<bool>(m_lhs ^ rhs), m_lhs, \"^\", rhs};\n    }\n\n    template <typename RhsT>\n    auto operator&&(RhsT const &) -> BinaryExpr<LhsT, RhsT const &> const {\n        static_assert(always_false<RhsT>::value,\n                      \"operator&& is not supported inside assertions, \"\n                      \"wrap the expression inside parentheses, or decompose it\");\n    }\n\n    template <typename RhsT>\n    auto operator||(RhsT const &) -> BinaryExpr<LhsT, RhsT const &> const {\n        static_assert(always_false<RhsT>::value,\n                      \"operator|| is not supported inside assertions, \"\n                      \"wrap the expression inside parentheses, or decompose it\");\n    }\n\n    auto makeUnaryExpr() const -> UnaryExpr<LhsT> { return UnaryExpr<LhsT>{m_lhs}; }\n};\n\nvoid handleExpression(ITransientExpression const &expr);\n\ntemplate <typename T>\nvoid handleExpression(ExprLhs<T> const &expr) {\n    handleExpression(expr.makeUnaryExpr());\n}\n\nstruct Decomposer {\n    template <typename T>\n    auto operator<=(T const &lhs) -> ExprLhs<T const &> {\n        return ExprLhs<T const &>{lhs};\n    }\n\n    auto operator<=(bool value) -> ExprLhs<bool> { return ExprLhs<bool>{value}; }\n};\n\n}  // end namespace Catch\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n// end catch_decomposer.h\n// start catch_interfaces_capture.h\n\n#include <chrono>\n#include <string>\n\nnamespace Catch {\n\nclass AssertionResult;\nstruct AssertionInfo;\nstruct SectionInfo;\nstruct SectionEndInfo;\nstruct MessageInfo;\nstruct MessageBuilder;\nstruct Counts;\nstruct AssertionReaction;\nstruct SourceLineInfo;\n\nstruct ITransientExpression;\nstruct IGeneratorTracker;\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\nstruct BenchmarkInfo;\ntemplate <typename Duration = std::chrono::duration<double, std::nano>>\nstruct BenchmarkStats;\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\nstruct IResultCapture {\n    virtual ~IResultCapture();\n\n    virtual bool sectionStarted(SectionInfo const &sectionInfo, Counts &assertions) = 0;\n    virtual void sectionEnded(SectionEndInfo const &endInfo) = 0;\n    virtual void sectionEndedEarly(SectionEndInfo const &endInfo) = 0;\n\n    virtual auto acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const &lineInfo)\n            -> IGeneratorTracker & = 0;\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n    virtual void benchmarkPreparing(std::string const &name) = 0;\n    virtual void benchmarkStarting(BenchmarkInfo const &info) = 0;\n    virtual void benchmarkEnded(BenchmarkStats<> const &stats) = 0;\n    virtual void benchmarkFailed(std::string const &error) = 0;\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    virtual void pushScopedMessage(MessageInfo const &message) = 0;\n    virtual void popScopedMessage(MessageInfo const &message) = 0;\n\n    virtual void emplaceUnscopedMessage(MessageBuilder const &builder) = 0;\n\n    virtual void handleFatalErrorCondition(StringRef message) = 0;\n\n    virtual void handleExpr(AssertionInfo const &info,\n                            ITransientExpression const &expr,\n                            AssertionReaction &reaction) = 0;\n    virtual void handleMessage(AssertionInfo const &info,\n                               ResultWas::OfType resultType,\n                               StringRef const &message,\n                               AssertionReaction &reaction) = 0;\n    virtual void handleUnexpectedExceptionNotThrown(AssertionInfo const &info,\n                                                    AssertionReaction &reaction) = 0;\n    virtual void handleUnexpectedInflightException(AssertionInfo const &info,\n                                                   std::string const &message,\n                                                   AssertionReaction &reaction) = 0;\n    virtual void handleIncomplete(AssertionInfo const &info) = 0;\n    virtual void handleNonExpr(AssertionInfo const &info,\n                               ResultWas::OfType resultType,\n                               AssertionReaction &reaction) = 0;\n\n    virtual bool lastAssertionPassed() = 0;\n    virtual void assertionPassed() = 0;\n\n    // Deprecated, do not use:\n    virtual std::string getCurrentTestName() const = 0;\n    virtual const AssertionResult *getLastResult() const = 0;\n    virtual void exceptionEarlyReported() = 0;\n};\n\nIResultCapture &getResultCapture();\n}  // namespace Catch\n\n// end catch_interfaces_capture.h\nnamespace Catch {\n\nstruct TestFailureException {};\nstruct AssertionResultData;\nstruct IResultCapture;\nclass RunContext;\n\nclass LazyExpression {\n    friend class AssertionHandler;\n    friend struct AssertionStats;\n    friend class RunContext;\n\n    ITransientExpression const *m_transientExpression = nullptr;\n    bool m_isNegated;\n\npublic:\n    LazyExpression(bool isNegated);\n    LazyExpression(LazyExpression const &other);\n    LazyExpression &operator=(LazyExpression const &) = delete;\n\n    explicit operator bool() const;\n\n    friend auto operator<<(std::ostream &os, LazyExpression const &lazyExpr) -> std::ostream &;\n};\n\nstruct AssertionReaction {\n    bool shouldDebugBreak = false;\n    bool shouldThrow = false;\n};\n\nclass AssertionHandler {\n    AssertionInfo m_assertionInfo;\n    AssertionReaction m_reaction;\n    bool m_completed = false;\n    IResultCapture &m_resultCapture;\n\npublic:\n    AssertionHandler(StringRef const &macroName,\n                     SourceLineInfo const &lineInfo,\n                     StringRef capturedExpression,\n                     ResultDisposition::Flags resultDisposition);\n    ~AssertionHandler() {\n        if (!m_completed) {\n            m_resultCapture.handleIncomplete(m_assertionInfo);\n        }\n    }\n\n    template <typename T>\n    void handleExpr(ExprLhs<T> const &expr) {\n        handleExpr(expr.makeUnaryExpr());\n    }\n    void handleExpr(ITransientExpression const &expr);\n\n    void handleMessage(ResultWas::OfType resultType, StringRef const &message);\n\n    void handleExceptionThrownAsExpected();\n    void handleUnexpectedExceptionNotThrown();\n    void handleExceptionNotThrownAsExpected();\n    void handleThrowingCallSkipped();\n    void handleUnexpectedInflightException();\n\n    void complete();\n    void setCompleted();\n\n    // query\n    auto allowThrows() const -> bool;\n};\n\nvoid handleExceptionMatchExpr(AssertionHandler &handler,\n                              std::string const &str,\n                              StringRef const &matcherString);\n\n}  // namespace Catch\n\n// end catch_assertionhandler.h\n// start catch_message.h\n\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\nstruct MessageInfo {\n    MessageInfo(StringRef const &_macroName,\n                SourceLineInfo const &_lineInfo,\n                ResultWas::OfType _type);\n\n    StringRef macroName;\n    std::string message;\n    SourceLineInfo lineInfo;\n    ResultWas::OfType type;\n    unsigned int sequence;\n\n    bool operator==(MessageInfo const &other) const;\n    bool operator<(MessageInfo const &other) const;\n\nprivate:\n    static unsigned int globalCount;\n};\n\nstruct MessageStream {\n    template <typename T>\n    MessageStream &operator<<(T const &value) {\n        m_stream << value;\n        return *this;\n    }\n\n    ReusableStringStream m_stream;\n};\n\nstruct MessageBuilder : MessageStream {\n    MessageBuilder(StringRef const &macroName,\n                   SourceLineInfo const &lineInfo,\n                   ResultWas::OfType type);\n\n    template <typename T>\n    MessageBuilder &operator<<(T const &value) {\n        m_stream << value;\n        return *this;\n    }\n\n    MessageInfo m_info;\n};\n\nclass ScopedMessage {\npublic:\n    explicit ScopedMessage(MessageBuilder const &builder);\n    ScopedMessage(ScopedMessage &duplicate) = delete;\n    ScopedMessage(ScopedMessage &&old);\n    ~ScopedMessage();\n\n    MessageInfo m_info;\n    bool m_moved;\n};\n\nclass Capturer {\n    std::vector<MessageInfo> m_messages;\n    IResultCapture &m_resultCapture = getResultCapture();\n    size_t m_captured = 0;\n\npublic:\n    Capturer(StringRef macroName,\n             SourceLineInfo const &lineInfo,\n             ResultWas::OfType resultType,\n             StringRef names);\n    ~Capturer();\n\n    void captureValue(size_t index, std::string const &value);\n\n    template <typename T>\n    void captureValues(size_t index, T const &value) {\n        captureValue(index, Catch::Detail::stringify(value));\n    }\n\n    template <typename T, typename... Ts>\n    void captureValues(size_t index, T const &value, Ts const &...values) {\n        captureValue(index, Catch::Detail::stringify(value));\n        captureValues(index + 1, values...);\n    }\n};\n\n}  // end namespace Catch\n\n// end catch_message.h\n#if !defined(CATCH_CONFIG_DISABLE)\n\n#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION)\n#define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__\n#else\n#define CATCH_INTERNAL_STRINGIFY(...) \"Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION\"\n#endif\n\n#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n\n///////////////////////////////////////////////////////////////////////////////\n// Another way to speed-up compilation is to omit local try-catch for REQUIRE*\n// macros.\n#define INTERNAL_CATCH_TRY\n#define INTERNAL_CATCH_CATCH(capturer)\n\n#else  // CATCH_CONFIG_FAST_COMPILE\n\n#define INTERNAL_CATCH_TRY try\n#define INTERNAL_CATCH_CATCH(handler)                \\\n    catch (...) {                                    \\\n        handler.handleUnexpectedInflightException(); \\\n    }\n\n#endif\n\n#define INTERNAL_CATCH_REACT(handler) handler.complete();\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_TEST(macroName, resultDisposition, ...)                    \\\n    do {                                                                          \\\n        CATCH_INTERNAL_IGNORE_BUT_WARN(__VA_ARGS__);                              \\\n        Catch::AssertionHandler catchAssertionHandler(                            \\\n                macroName##_catch_sr, CATCH_INTERNAL_LINEINFO,                    \\\n                CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition);        \\\n        INTERNAL_CATCH_TRY {                                                      \\\n            CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                             \\\n            CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS                          \\\n            catchAssertionHandler.handleExpr(Catch::Decomposer() <= __VA_ARGS__); \\\n            CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                              \\\n        }                                                                         \\\n        INTERNAL_CATCH_CATCH(catchAssertionHandler)                               \\\n        INTERNAL_CATCH_REACT(catchAssertionHandler)                               \\\n    } while ((void)0, (false) && static_cast<bool>(!!(__VA_ARGS__)))\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_IF(macroName, resultDisposition, ...)        \\\n    INTERNAL_CATCH_TEST(macroName, resultDisposition, __VA_ARGS__); \\\n    if (Catch::getResultCapture().lastAssertionPassed())\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_ELSE(macroName, resultDisposition, ...)      \\\n    INTERNAL_CATCH_TEST(macroName, resultDisposition, __VA_ARGS__); \\\n    if (!Catch::getResultCapture().lastAssertionPassed())\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_NO_THROW(macroName, resultDisposition, ...)         \\\n    do {                                                                   \\\n        Catch::AssertionHandler catchAssertionHandler(                     \\\n                macroName##_catch_sr, CATCH_INTERNAL_LINEINFO,             \\\n                CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \\\n        try {                                                              \\\n            static_cast<void>(__VA_ARGS__);                                \\\n            catchAssertionHandler.handleExceptionNotThrownAsExpected();    \\\n        } catch (...) {                                                    \\\n            catchAssertionHandler.handleUnexpectedInflightException();     \\\n        }                                                                  \\\n        INTERNAL_CATCH_REACT(catchAssertionHandler)                        \\\n    } while (false)\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_THROWS(macroName, resultDisposition, ...)            \\\n    do {                                                                    \\\n        Catch::AssertionHandler catchAssertionHandler(                      \\\n                macroName##_catch_sr, CATCH_INTERNAL_LINEINFO,              \\\n                CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition);  \\\n        if (catchAssertionHandler.allowThrows())                            \\\n            try {                                                           \\\n                static_cast<void>(__VA_ARGS__);                             \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \\\n            } catch (...) {                                                 \\\n                catchAssertionHandler.handleExceptionThrownAsExpected();    \\\n            }                                                               \\\n        else                                                                \\\n            catchAssertionHandler.handleThrowingCallSkipped();              \\\n        INTERNAL_CATCH_REACT(catchAssertionHandler)                         \\\n    } while (false)\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_THROWS_AS(macroName, exceptionType, resultDisposition, expr)          \\\n    do {                                                                                     \\\n        Catch::AssertionHandler catchAssertionHandler(                                       \\\n                macroName##_catch_sr, CATCH_INTERNAL_LINEINFO,                               \\\n                CATCH_INTERNAL_STRINGIFY(expr) \", \" CATCH_INTERNAL_STRINGIFY(exceptionType), \\\n                resultDisposition);                                                          \\\n        if (catchAssertionHandler.allowThrows())                                             \\\n            try {                                                                            \\\n                static_cast<void>(expr);                                                     \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown();                  \\\n            } catch (exceptionType const &) {                                                \\\n                catchAssertionHandler.handleExceptionThrownAsExpected();                     \\\n            } catch (...) {                                                                  \\\n                catchAssertionHandler.handleUnexpectedInflightException();                   \\\n            }                                                                                \\\n        else                                                                                 \\\n            catchAssertionHandler.handleThrowingCallSkipped();                               \\\n        INTERNAL_CATCH_REACT(catchAssertionHandler)                                          \\\n    } while (false)\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_MSG(macroName, messageType, resultDisposition, ...)                         \\\n    do {                                                                                           \\\n        Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr,                        \\\n                                                      CATCH_INTERNAL_LINEINFO, Catch::StringRef(), \\\n                                                      resultDisposition);                          \\\n        catchAssertionHandler.handleMessage(                                                       \\\n                messageType, (Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop())    \\\n                                     .m_stream.str());                                             \\\n        INTERNAL_CATCH_REACT(catchAssertionHandler)                                                \\\n    } while (false)\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_CAPTURE(varName, macroName, ...)                                        \\\n    auto varName = Catch::Capturer(macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info, \\\n                                   #__VA_ARGS__);                                              \\\n    varName.captureValues(0, __VA_ARGS__)\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_INFO(macroName, log)                                      \\\n    Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME(scopedMessage)(              \\\n            Catch::MessageBuilder(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, \\\n                                  Catch::ResultWas::Info)                        \\\n            << log);\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_UNSCOPED_INFO(macroName, log)                             \\\n    Catch::getResultCapture().emplaceUnscopedMessage(                            \\\n            Catch::MessageBuilder(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, \\\n                                  Catch::ResultWas::Info)                        \\\n            << log)\n\n///////////////////////////////////////////////////////////////////////////////\n// Although this is matcher-based, it can be used with just a string\n#define INTERNAL_CATCH_THROWS_STR_MATCHES(macroName, resultDisposition, matcher, ...)         \\\n    do {                                                                                      \\\n        Catch::AssertionHandler catchAssertionHandler(                                        \\\n                macroName##_catch_sr, CATCH_INTERNAL_LINEINFO,                                \\\n                CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) \", \" CATCH_INTERNAL_STRINGIFY(matcher), \\\n                resultDisposition);                                                           \\\n        if (catchAssertionHandler.allowThrows())                                              \\\n            try {                                                                             \\\n                static_cast<void>(__VA_ARGS__);                                               \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown();                   \\\n            } catch (...) {                                                                   \\\n                Catch::handleExceptionMatchExpr(catchAssertionHandler, matcher,               \\\n                                                #matcher##_catch_sr);                         \\\n            }                                                                                 \\\n        else                                                                                  \\\n            catchAssertionHandler.handleThrowingCallSkipped();                                \\\n        INTERNAL_CATCH_REACT(catchAssertionHandler)                                           \\\n    } while (false)\n\n#endif  // CATCH_CONFIG_DISABLE\n\n// end catch_capture.hpp\n// start catch_section.h\n\n// start catch_section_info.h\n\n// start catch_totals.h\n\n#include <cstddef>\n\nnamespace Catch {\n\nstruct Counts {\n    Counts operator-(Counts const &other) const;\n    Counts &operator+=(Counts const &other);\n\n    std::size_t total() const;\n    bool allPassed() const;\n    bool allOk() const;\n\n    std::size_t passed = 0;\n    std::size_t failed = 0;\n    std::size_t failedButOk = 0;\n};\n\nstruct Totals {\n    Totals operator-(Totals const &other) const;\n    Totals &operator+=(Totals const &other);\n\n    Totals delta(Totals const &prevTotals) const;\n\n    int error = 0;\n    Counts assertions;\n    Counts testCases;\n};\n}  // namespace Catch\n\n// end catch_totals.h\n#include <string>\n\nnamespace Catch {\n\nstruct SectionInfo {\n    SectionInfo(SourceLineInfo const &_lineInfo, std::string const &_name);\n\n    // Deprecated\n    SectionInfo(SourceLineInfo const &_lineInfo, std::string const &_name, std::string const &)\n            : SectionInfo(_lineInfo, _name) {}\n\n    std::string name;\n    std::string description;  // !Deprecated: this will always be empty\n    SourceLineInfo lineInfo;\n};\n\nstruct SectionEndInfo {\n    SectionInfo sectionInfo;\n    Counts prevAssertions;\n    double durationInSeconds;\n};\n\n}  // end namespace Catch\n\n// end catch_section_info.h\n// start catch_timer.h\n\n#include <cstdint>\n\nnamespace Catch {\n\nauto getCurrentNanosecondsSinceEpoch() -> uint64_t;\nauto getEstimatedClockResolution() -> uint64_t;\n\nclass Timer {\n    uint64_t m_nanoseconds = 0;\n\npublic:\n    void start();\n    auto getElapsedNanoseconds() const -> uint64_t;\n    auto getElapsedMicroseconds() const -> uint64_t;\n    auto getElapsedMilliseconds() const -> unsigned int;\n    auto getElapsedSeconds() const -> double;\n};\n\n}  // namespace Catch\n\n// end catch_timer.h\n#include <string>\n\nnamespace Catch {\n\nclass Section : NonCopyable {\npublic:\n    Section(SectionInfo const &info);\n    ~Section();\n\n    // This indicates whether the section should be executed or not\n    explicit operator bool() const;\n\nprivate:\n    SectionInfo m_info;\n\n    std::string m_name;\n    Counts m_assertions;\n    bool m_sectionIncluded;\n    Timer m_timer;\n};\n\n}  // end namespace Catch\n\n#define INTERNAL_CATCH_SECTION(...)                                                \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                                      \\\n    CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS                                        \\\n    if (Catch::Section const &INTERNAL_CATCH_UNIQUE_NAME(catch_internal_Section) = \\\n                Catch::SectionInfo(CATCH_INTERNAL_LINEINFO, __VA_ARGS__))          \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n#define INTERNAL_CATCH_DYNAMIC_SECTION(...)                                               \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                                             \\\n    CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS                                               \\\n    if (Catch::Section const &INTERNAL_CATCH_UNIQUE_NAME(catch_internal_Section) =        \\\n                Catch::SectionInfo(CATCH_INTERNAL_LINEINFO,                               \\\n                                   (Catch::ReusableStringStream() << __VA_ARGS__).str())) \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n// end catch_section.h\n// start catch_interfaces_exception.h\n\n// start catch_interfaces_registry_hub.h\n\n#include <memory>\n#include <string>\n\nnamespace Catch {\n\nclass TestCase;\nstruct ITestCaseRegistry;\nstruct IExceptionTranslatorRegistry;\nstruct IExceptionTranslator;\nstruct IReporterRegistry;\nstruct IReporterFactory;\nstruct ITagAliasRegistry;\nstruct IMutableEnumValuesRegistry;\n\nclass StartupExceptionRegistry;\n\nusing IReporterFactoryPtr = std::shared_ptr<IReporterFactory>;\n\nstruct IRegistryHub {\n    virtual ~IRegistryHub();\n\n    virtual IReporterRegistry const &getReporterRegistry() const = 0;\n    virtual ITestCaseRegistry const &getTestCaseRegistry() const = 0;\n    virtual ITagAliasRegistry const &getTagAliasRegistry() const = 0;\n    virtual IExceptionTranslatorRegistry const &getExceptionTranslatorRegistry() const = 0;\n\n    virtual StartupExceptionRegistry const &getStartupExceptionRegistry() const = 0;\n};\n\nstruct IMutableRegistryHub {\n    virtual ~IMutableRegistryHub();\n    virtual void registerReporter(std::string const &name, IReporterFactoryPtr const &factory) = 0;\n    virtual void registerListener(IReporterFactoryPtr const &factory) = 0;\n    virtual void registerTest(TestCase const &testInfo) = 0;\n    virtual void registerTranslator(const IExceptionTranslator *translator) = 0;\n    virtual void registerTagAlias(std::string const &alias,\n                                  std::string const &tag,\n                                  SourceLineInfo const &lineInfo) = 0;\n    virtual void registerStartupException() noexcept = 0;\n    virtual IMutableEnumValuesRegistry &getMutableEnumValuesRegistry() = 0;\n};\n\nIRegistryHub const &getRegistryHub();\nIMutableRegistryHub &getMutableRegistryHub();\nvoid cleanUp();\nstd::string translateActiveException();\n\n}  // namespace Catch\n\n// end catch_interfaces_registry_hub.h\n#if defined(CATCH_CONFIG_DISABLE)\n#define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG(translatorName, signature) \\\n    static std::string translatorName(signature)\n#endif\n\n#include <exception>\n#include <string>\n#include <vector>\n\nnamespace Catch {\nusing exceptionTranslateFunction = std::string (*)();\n\nstruct IExceptionTranslator;\nusing ExceptionTranslators = std::vector<std::unique_ptr<IExceptionTranslator const>>;\n\nstruct IExceptionTranslator {\n    virtual ~IExceptionTranslator();\n    virtual std::string translate(ExceptionTranslators::const_iterator it,\n                                  ExceptionTranslators::const_iterator itEnd) const = 0;\n};\n\nstruct IExceptionTranslatorRegistry {\n    virtual ~IExceptionTranslatorRegistry();\n\n    virtual std::string translateActiveException() const = 0;\n};\n\nclass ExceptionTranslatorRegistrar {\n    template <typename T>\n    class ExceptionTranslator : public IExceptionTranslator {\n    public:\n        ExceptionTranslator(std::string (*translateFunction)(T &))\n                : m_translateFunction(translateFunction) {}\n\n        std::string translate(ExceptionTranslators::const_iterator it,\n                              ExceptionTranslators::const_iterator itEnd) const override {\n#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n            return \"\";\n#else\n            try {\n                if (it == itEnd)\n                    std::rethrow_exception(std::current_exception());\n                else\n                    return (*it)->translate(it + 1, itEnd);\n            } catch (T &ex) {\n                return m_translateFunction(ex);\n            }\n#endif\n        }\n\n    protected:\n        std::string (*m_translateFunction)(T &);\n    };\n\npublic:\n    template <typename T>\n    ExceptionTranslatorRegistrar(std::string (*translateFunction)(T &)) {\n        getMutableRegistryHub().registerTranslator(new ExceptionTranslator<T>(translateFunction));\n    }\n};\n}  // namespace Catch\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2(translatorName, signature) \\\n    static std::string translatorName(signature);                      \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                          \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                           \\\n    namespace {                                                        \\\n    Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME(    \\\n            catch_internal_ExceptionRegistrar)(&translatorName);       \\\n    }                                                                  \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                           \\\n    static std::string translatorName(signature)\n\n#define INTERNAL_CATCH_TRANSLATE_EXCEPTION(signature) \\\n    INTERNAL_CATCH_TRANSLATE_EXCEPTION2(              \\\n            INTERNAL_CATCH_UNIQUE_NAME(catch_internal_ExceptionTranslator), signature)\n\n// end catch_interfaces_exception.h\n// start catch_approx.h\n\n#include <type_traits>\n\nnamespace Catch {\nnamespace Detail {\n\nclass Approx {\nprivate:\n    bool equalityComparisonImpl(double other) const;\n    // Validates the new margin (margin >= 0)\n    // out-of-line to avoid including stdexcept in the header\n    void setMargin(double margin);\n    // Validates the new epsilon (0 < epsilon < 1)\n    // out-of-line to avoid including stdexcept in the header\n    void setEpsilon(double epsilon);\n\npublic:\n    explicit Approx(double value);\n\n    static Approx custom();\n\n    Approx operator-() const;\n\n    template <typename T,\n              typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    Approx operator()(T const &value) {\n        Approx approx(static_cast<double>(value));\n        approx.m_epsilon = m_epsilon;\n        approx.m_margin = m_margin;\n        approx.m_scale = m_scale;\n        return approx;\n    }\n\n    template <typename T,\n              typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    explicit Approx(T const &value) : Approx(static_cast<double>(value)) {}\n\n    template <typename T,\n              typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    friend bool operator==(const T &lhs, Approx const &rhs) {\n        auto lhs_v = static_cast<double>(lhs);\n        return rhs.equalityComparisonImpl(lhs_v);\n    }\n\n    template <typename T,\n              typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    friend bool operator==(Approx const &lhs, const T &rhs) {\n        return operator==(rhs, lhs);\n    }\n\n    template <typename T,\n              typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    friend bool operator!=(T const &lhs, Approx const &rhs) {\n        return !operator==(lhs, rhs);\n    }\n\n    template <typename T,\n              typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    friend bool operator!=(Approx const &lhs, T const &rhs) {\n        return !operator==(rhs, lhs);\n    }\n\n    template <typename T,\n              typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    friend bool operator<=(T const &lhs, Approx const &rhs) {\n        return static_cast<double>(lhs) < rhs.m_value || lhs == rhs;\n    }\n\n    template <typename T,\n              typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    friend bool operator<=(Approx const &lhs, T const &rhs) {\n        return lhs.m_value < static_cast<double>(rhs) || lhs == rhs;\n    }\n\n    template <typename T,\n              typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    friend bool operator>=(T const &lhs, Approx const &rhs) {\n        return static_cast<double>(lhs) > rhs.m_value || lhs == rhs;\n    }\n\n    template <typename T,\n              typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    friend bool operator>=(Approx const &lhs, T const &rhs) {\n        return lhs.m_value > static_cast<double>(rhs) || lhs == rhs;\n    }\n\n    template <typename T,\n              typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    Approx &epsilon(T const &newEpsilon) {\n        double epsilonAsDouble = static_cast<double>(newEpsilon);\n        setEpsilon(epsilonAsDouble);\n        return *this;\n    }\n\n    template <typename T,\n              typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    Approx &margin(T const &newMargin) {\n        double marginAsDouble = static_cast<double>(newMargin);\n        setMargin(marginAsDouble);\n        return *this;\n    }\n\n    template <typename T,\n              typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    Approx &scale(T const &newScale) {\n        m_scale = static_cast<double>(newScale);\n        return *this;\n    }\n\n    std::string toString() const;\n\nprivate:\n    double m_epsilon;\n    double m_margin;\n    double m_scale;\n    double m_value;\n};\n}  // end namespace Detail\n\nnamespace literals {\nDetail::Approx operator\"\" _a(long double val);\nDetail::Approx operator\"\" _a(unsigned long long val);\n}  // end namespace literals\n\ntemplate <>\nstruct StringMaker<Catch::Detail::Approx> {\n    static std::string convert(Catch::Detail::Approx const &value);\n};\n\n}  // end namespace Catch\n\n// end catch_approx.h\n// start catch_string_manip.h\n\n#include <iosfwd>\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\nbool startsWith(std::string const &s, std::string const &prefix);\nbool startsWith(std::string const &s, char prefix);\nbool endsWith(std::string const &s, std::string const &suffix);\nbool endsWith(std::string const &s, char suffix);\nbool contains(std::string const &s, std::string const &infix);\nvoid toLowerInPlace(std::string &s);\nstd::string toLower(std::string const &s);\n//! Returns a new string without whitespace at the start/end\nstd::string trim(std::string const &str);\n//! Returns a substring of the original ref without whitespace. Beware\n//! lifetimes!\nStringRef trim(StringRef ref);\n\n// !!! Be aware, returns refs into original string - make sure original string\n// outlives them\nstd::vector<StringRef> splitStringRef(StringRef str, char delimiter);\nbool replaceInPlace(std::string &str, std::string const &replaceThis, std::string const &withThis);\n\nstruct pluralise {\n    pluralise(std::size_t count, std::string const &label);\n\n    friend std::ostream &operator<<(std::ostream &os, pluralise const &pluraliser);\n\n    std::size_t m_count;\n    std::string m_label;\n};\n}  // namespace Catch\n\n// end catch_string_manip.h\n#ifndef CATCH_CONFIG_DISABLE_MATCHERS\n// start catch_capture_matchers.h\n\n// start catch_matchers.h\n\n#include <string>\n#include <vector>\n\nnamespace Catch {\nnamespace Matchers {\nnamespace Impl {\n\ntemplate <typename ArgT>\nstruct MatchAllOf;\ntemplate <typename ArgT>\nstruct MatchAnyOf;\ntemplate <typename ArgT>\nstruct MatchNotOf;\n\nclass MatcherUntypedBase {\npublic:\n    MatcherUntypedBase() = default;\n    MatcherUntypedBase(MatcherUntypedBase const &) = default;\n    MatcherUntypedBase &operator=(MatcherUntypedBase const &) = delete;\n    std::string toString() const;\n\nprotected:\n    virtual ~MatcherUntypedBase();\n    virtual std::string describe() const = 0;\n    mutable std::string m_cachedToString;\n};\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wnon-virtual-dtor\"\n#endif\n\ntemplate <typename ObjectT>\nstruct MatcherMethod {\n    virtual bool match(ObjectT const &arg) const = 0;\n};\n\n#if defined(__OBJC__)\n// Hack to fix Catch GH issue #1661. Could use id for generic Object support.\n// use of const for Object pointers is very uncommon and under ARC it causes\n// some kind of signature mismatch that breaks compilation\ntemplate <>\nstruct MatcherMethod<NSString *> {\n    virtual bool match(NSString *arg) const = 0;\n};\n#endif\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\ntemplate <typename T>\nstruct MatcherBase : MatcherUntypedBase, MatcherMethod<T> {\n    MatchAllOf<T> operator&&(MatcherBase const &other) const;\n    MatchAnyOf<T> operator||(MatcherBase const &other) const;\n    MatchNotOf<T> operator!() const;\n};\n\ntemplate <typename ArgT>\nstruct MatchAllOf : MatcherBase<ArgT> {\n    bool match(ArgT const &arg) const override {\n        for (auto matcher : m_matchers) {\n            if (!matcher->match(arg))\n                return false;\n        }\n        return true;\n    }\n    std::string describe() const override {\n        std::string description;\n        description.reserve(4 + m_matchers.size() * 32);\n        description += \"( \";\n        bool first = true;\n        for (auto matcher : m_matchers) {\n            if (first)\n                first = false;\n            else\n                description += \" and \";\n            description += matcher->toString();\n        }\n        description += \" )\";\n        return description;\n    }\n\n    MatchAllOf<ArgT> operator&&(MatcherBase<ArgT> const &other) {\n        auto copy(*this);\n        copy.m_matchers.push_back(&other);\n        return copy;\n    }\n\n    std::vector<MatcherBase<ArgT> const *> m_matchers;\n};\ntemplate <typename ArgT>\nstruct MatchAnyOf : MatcherBase<ArgT> {\n    bool match(ArgT const &arg) const override {\n        for (auto matcher : m_matchers) {\n            if (matcher->match(arg))\n                return true;\n        }\n        return false;\n    }\n    std::string describe() const override {\n        std::string description;\n        description.reserve(4 + m_matchers.size() * 32);\n        description += \"( \";\n        bool first = true;\n        for (auto matcher : m_matchers) {\n            if (first)\n                first = false;\n            else\n                description += \" or \";\n            description += matcher->toString();\n        }\n        description += \" )\";\n        return description;\n    }\n\n    MatchAnyOf<ArgT> operator||(MatcherBase<ArgT> const &other) {\n        auto copy(*this);\n        copy.m_matchers.push_back(&other);\n        return copy;\n    }\n\n    std::vector<MatcherBase<ArgT> const *> m_matchers;\n};\n\ntemplate <typename ArgT>\nstruct MatchNotOf : MatcherBase<ArgT> {\n    MatchNotOf(MatcherBase<ArgT> const &underlyingMatcher)\n            : m_underlyingMatcher(underlyingMatcher) {}\n\n    bool match(ArgT const &arg) const override { return !m_underlyingMatcher.match(arg); }\n\n    std::string describe() const override { return \"not \" + m_underlyingMatcher.toString(); }\n    MatcherBase<ArgT> const &m_underlyingMatcher;\n};\n\ntemplate <typename T>\nMatchAllOf<T> MatcherBase<T>::operator&&(MatcherBase const &other) const {\n    return MatchAllOf<T>() && *this && other;\n}\ntemplate <typename T>\nMatchAnyOf<T> MatcherBase<T>::operator||(MatcherBase const &other) const {\n    return MatchAnyOf<T>() || *this || other;\n}\ntemplate <typename T>\nMatchNotOf<T> MatcherBase<T>::operator!() const {\n    return MatchNotOf<T>(*this);\n}\n\n}  // namespace Impl\n\n}  // namespace Matchers\n\nusing namespace Matchers;\nusing Matchers::Impl::MatcherBase;\n\n}  // namespace Catch\n\n// end catch_matchers.h\n// start catch_matchers_exception.hpp\n\nnamespace Catch {\nnamespace Matchers {\nnamespace Exception {\n\nclass ExceptionMessageMatcher : public MatcherBase<std::exception> {\n    std::string m_message;\n\npublic:\n    ExceptionMessageMatcher(std::string const &message) : m_message(message) {}\n\n    bool match(std::exception const &ex) const override;\n\n    std::string describe() const override;\n};\n\n}  // namespace Exception\n\nException::ExceptionMessageMatcher Message(std::string const &message);\n\n}  // namespace Matchers\n}  // namespace Catch\n\n// end catch_matchers_exception.hpp\n// start catch_matchers_floating.h\n\nnamespace Catch {\nnamespace Matchers {\n\nnamespace Floating {\n\nenum class FloatingPointKind : uint8_t;\n\nstruct WithinAbsMatcher : MatcherBase<double> {\n    WithinAbsMatcher(double target, double margin);\n    bool match(double const &matchee) const override;\n    std::string describe() const override;\n\nprivate:\n    double m_target;\n    double m_margin;\n};\n\nstruct WithinUlpsMatcher : MatcherBase<double> {\n    WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType);\n    bool match(double const &matchee) const override;\n    std::string describe() const override;\n\nprivate:\n    double m_target;\n    uint64_t m_ulps;\n    FloatingPointKind m_type;\n};\n\n// Given IEEE-754 format for floats and doubles, we can assume\n// that float -> double promotion is lossless. Given this, we can\n// assume that if we do the standard relative comparison of\n// |lhs - rhs| <= epsilon * max(fabs(lhs), fabs(rhs)), then we get\n// the same result if we do this for floats, as if we do this for\n// doubles that were promoted from floats.\nstruct WithinRelMatcher : MatcherBase<double> {\n    WithinRelMatcher(double target, double epsilon);\n    bool match(double const &matchee) const override;\n    std::string describe() const override;\n\nprivate:\n    double m_target;\n    double m_epsilon;\n};\n\n}  // namespace Floating\n\n// The following functions create the actual matcher objects.\n// This allows the types to be inferred\nFloating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff);\nFloating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff);\nFloating::WithinAbsMatcher WithinAbs(double target, double margin);\nFloating::WithinRelMatcher WithinRel(double target, double eps);\n// defaults epsilon to 100*numeric_limits<double>::epsilon()\nFloating::WithinRelMatcher WithinRel(double target);\nFloating::WithinRelMatcher WithinRel(float target, float eps);\n// defaults epsilon to 100*numeric_limits<float>::epsilon()\nFloating::WithinRelMatcher WithinRel(float target);\n\n}  // namespace Matchers\n}  // namespace Catch\n\n// end catch_matchers_floating.h\n// start catch_matchers_generic.hpp\n\n#include <functional>\n#include <string>\n\nnamespace Catch {\nnamespace Matchers {\nnamespace Generic {\n\nnamespace Detail {\nstd::string finalizeDescription(const std::string &desc);\n}\n\ntemplate <typename T>\nclass PredicateMatcher : public MatcherBase<T> {\n    std::function<bool(T const &)> m_predicate;\n    std::string m_description;\n\npublic:\n    PredicateMatcher(std::function<bool(T const &)> const &elem, std::string const &descr)\n            : m_predicate(std::move(elem)), m_description(Detail::finalizeDescription(descr)) {}\n\n    bool match(T const &item) const override { return m_predicate(item); }\n\n    std::string describe() const override { return m_description; }\n};\n\n}  // namespace Generic\n\n// The following functions create the actual matcher objects.\n// The user has to explicitly specify type to the function, because\n// inferring std::function<bool(T const&)> is hard (but possible) and\n// requires a lot of TMP.\ntemplate <typename T>\nGeneric::PredicateMatcher<T> Predicate(std::function<bool(T const &)> const &predicate,\n                                       std::string const &description = \"\") {\n    return Generic::PredicateMatcher<T>(predicate, description);\n}\n\n}  // namespace Matchers\n}  // namespace Catch\n\n// end catch_matchers_generic.hpp\n// start catch_matchers_string.h\n\n#include <string>\n\nnamespace Catch {\nnamespace Matchers {\n\nnamespace StdString {\n\nstruct CasedString {\n    CasedString(std::string const &str, CaseSensitive::Choice caseSensitivity);\n    std::string adjustString(std::string const &str) const;\n    std::string caseSensitivitySuffix() const;\n\n    CaseSensitive::Choice m_caseSensitivity;\n    std::string m_str;\n};\n\nstruct StringMatcherBase : MatcherBase<std::string> {\n    StringMatcherBase(std::string const &operation, CasedString const &comparator);\n    std::string describe() const override;\n\n    CasedString m_comparator;\n    std::string m_operation;\n};\n\nstruct EqualsMatcher : StringMatcherBase {\n    EqualsMatcher(CasedString const &comparator);\n    bool match(std::string const &source) const override;\n};\nstruct ContainsMatcher : StringMatcherBase {\n    ContainsMatcher(CasedString const &comparator);\n    bool match(std::string const &source) const override;\n};\nstruct StartsWithMatcher : StringMatcherBase {\n    StartsWithMatcher(CasedString const &comparator);\n    bool match(std::string const &source) const override;\n};\nstruct EndsWithMatcher : StringMatcherBase {\n    EndsWithMatcher(CasedString const &comparator);\n    bool match(std::string const &source) const override;\n};\n\nstruct RegexMatcher : MatcherBase<std::string> {\n    RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity);\n    bool match(std::string const &matchee) const override;\n    std::string describe() const override;\n\nprivate:\n    std::string m_regex;\n    CaseSensitive::Choice m_caseSensitivity;\n};\n\n}  // namespace StdString\n\n// The following functions create the actual matcher objects.\n// This allows the types to be inferred\n\nStdString::EqualsMatcher Equals(std::string const &str,\n                                CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes);\nStdString::ContainsMatcher Contains(std::string const &str,\n                                    CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes);\nStdString::EndsWithMatcher EndsWith(std::string const &str,\n                                    CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes);\nStdString::StartsWithMatcher StartsWith(std::string const &str,\n                                        CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes);\nStdString::RegexMatcher Matches(std::string const &regex,\n                                CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes);\n\n}  // namespace Matchers\n}  // namespace Catch\n\n// end catch_matchers_string.h\n// start catch_matchers_vector.h\n\n#include <algorithm>\n\nnamespace Catch {\nnamespace Matchers {\n\nnamespace Vector {\ntemplate <typename T, typename Alloc>\nstruct ContainsElementMatcher : MatcherBase<std::vector<T, Alloc>> {\n    ContainsElementMatcher(T const &comparator) : m_comparator(comparator) {}\n\n    bool match(std::vector<T, Alloc> const &v) const override {\n        for (auto const &el : v) {\n            if (el == m_comparator) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    std::string describe() const override {\n        return \"Contains: \" + ::Catch::Detail::stringify(m_comparator);\n    }\n\n    T const &m_comparator;\n};\n\ntemplate <typename T, typename AllocComp, typename AllocMatch>\nstruct ContainsMatcher : MatcherBase<std::vector<T, AllocMatch>> {\n    ContainsMatcher(std::vector<T, AllocComp> const &comparator) : m_comparator(comparator) {}\n\n    bool match(std::vector<T, AllocMatch> const &v) const override {\n        // !TBD: see note in EqualsMatcher\n        if (m_comparator.size() > v.size())\n            return false;\n        for (auto const &comparator : m_comparator) {\n            auto present = false;\n            for (const auto &el : v) {\n                if (el == comparator) {\n                    present = true;\n                    break;\n                }\n            }\n            if (!present) {\n                return false;\n            }\n        }\n        return true;\n    }\n    std::string describe() const override {\n        return \"Contains: \" + ::Catch::Detail::stringify(m_comparator);\n    }\n\n    std::vector<T, AllocComp> const &m_comparator;\n};\n\ntemplate <typename T, typename AllocComp, typename AllocMatch>\nstruct EqualsMatcher : MatcherBase<std::vector<T, AllocMatch>> {\n    EqualsMatcher(std::vector<T, AllocComp> const &comparator) : m_comparator(comparator) {}\n\n    bool match(std::vector<T, AllocMatch> const &v) const override {\n        // !TBD: This currently works if all elements can be compared using !=\n        // - a more general approach would be via a compare template that defaults\n        // to using !=. but could be specialised for, e.g. std::vector<T, Alloc> etc\n        // - then just call that directly\n        if (m_comparator.size() != v.size())\n            return false;\n        for (std::size_t i = 0; i < v.size(); ++i)\n            if (m_comparator[i] != v[i])\n                return false;\n        return true;\n    }\n    std::string describe() const override {\n        return \"Equals: \" + ::Catch::Detail::stringify(m_comparator);\n    }\n    std::vector<T, AllocComp> const &m_comparator;\n};\n\ntemplate <typename T, typename AllocComp, typename AllocMatch>\nstruct ApproxMatcher : MatcherBase<std::vector<T, AllocMatch>> {\n    ApproxMatcher(std::vector<T, AllocComp> const &comparator) : m_comparator(comparator) {}\n\n    bool match(std::vector<T, AllocMatch> const &v) const override {\n        if (m_comparator.size() != v.size())\n            return false;\n        for (std::size_t i = 0; i < v.size(); ++i)\n            if (m_comparator[i] != approx(v[i]))\n                return false;\n        return true;\n    }\n    std::string describe() const override {\n        return \"is approx: \" + ::Catch::Detail::stringify(m_comparator);\n    }\n    template <typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    ApproxMatcher &epsilon(T const &newEpsilon) {\n        approx.epsilon(newEpsilon);\n        return *this;\n    }\n    template <typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    ApproxMatcher &margin(T const &newMargin) {\n        approx.margin(newMargin);\n        return *this;\n    }\n    template <typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n    ApproxMatcher &scale(T const &newScale) {\n        approx.scale(newScale);\n        return *this;\n    }\n\n    std::vector<T, AllocComp> const &m_comparator;\n    mutable Catch::Detail::Approx approx = Catch::Detail::Approx::custom();\n};\n\ntemplate <typename T, typename AllocComp, typename AllocMatch>\nstruct UnorderedEqualsMatcher : MatcherBase<std::vector<T, AllocMatch>> {\n    UnorderedEqualsMatcher(std::vector<T, AllocComp> const &target) : m_target(target) {}\n    bool match(std::vector<T, AllocMatch> const &vec) const override {\n        if (m_target.size() != vec.size()) {\n            return false;\n        }\n        return std::is_permutation(m_target.begin(), m_target.end(), vec.begin());\n    }\n\n    std::string describe() const override {\n        return \"UnorderedEquals: \" + ::Catch::Detail::stringify(m_target);\n    }\n\nprivate:\n    std::vector<T, AllocComp> const &m_target;\n};\n\n}  // namespace Vector\n\n// The following functions create the actual matcher objects.\n// This allows the types to be inferred\n\ntemplate <typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\nVector::ContainsMatcher<T, AllocComp, AllocMatch> Contains(\n        std::vector<T, AllocComp> const &comparator) {\n    return Vector::ContainsMatcher<T, AllocComp, AllocMatch>(comparator);\n}\n\ntemplate <typename T, typename Alloc = std::allocator<T>>\nVector::ContainsElementMatcher<T, Alloc> VectorContains(T const &comparator) {\n    return Vector::ContainsElementMatcher<T, Alloc>(comparator);\n}\n\ntemplate <typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\nVector::EqualsMatcher<T, AllocComp, AllocMatch> Equals(\n        std::vector<T, AllocComp> const &comparator) {\n    return Vector::EqualsMatcher<T, AllocComp, AllocMatch>(comparator);\n}\n\ntemplate <typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\nVector::ApproxMatcher<T, AllocComp, AllocMatch> Approx(\n        std::vector<T, AllocComp> const &comparator) {\n    return Vector::ApproxMatcher<T, AllocComp, AllocMatch>(comparator);\n}\n\ntemplate <typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\nVector::UnorderedEqualsMatcher<T, AllocComp, AllocMatch> UnorderedEquals(\n        std::vector<T, AllocComp> const &target) {\n    return Vector::UnorderedEqualsMatcher<T, AllocComp, AllocMatch>(target);\n}\n\n}  // namespace Matchers\n}  // namespace Catch\n\n// end catch_matchers_vector.h\nnamespace Catch {\n\ntemplate <typename ArgT, typename MatcherT>\nclass MatchExpr : public ITransientExpression {\n    ArgT const &m_arg;\n    MatcherT m_matcher;\n    StringRef m_matcherString;\n\npublic:\n    MatchExpr(ArgT const &arg, MatcherT const &matcher, StringRef const &matcherString)\n            : ITransientExpression{true, matcher.match(arg)},\n              m_arg(arg),\n              m_matcher(matcher),\n              m_matcherString(matcherString) {}\n\n    void streamReconstructedExpression(std::ostream &os) const override {\n        auto matcherAsString = m_matcher.toString();\n        os << Catch::Detail::stringify(m_arg) << ' ';\n        if (matcherAsString == Detail::unprintableString)\n            os << m_matcherString;\n        else\n            os << matcherAsString;\n    }\n};\n\nusing StringMatcher = Matchers::Impl::MatcherBase<std::string>;\n\nvoid handleExceptionMatchExpr(AssertionHandler &handler,\n                              StringMatcher const &matcher,\n                              StringRef const &matcherString);\n\ntemplate <typename ArgT, typename MatcherT>\nauto makeMatchExpr(ArgT const &arg, MatcherT const &matcher, StringRef const &matcherString)\n        -> MatchExpr<ArgT, MatcherT> {\n    return MatchExpr<ArgT, MatcherT>(arg, matcher, matcherString);\n}\n\n}  // namespace Catch\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CHECK_THAT(macroName, matcher, resultDisposition, arg)               \\\n    do {                                                                              \\\n        Catch::AssertionHandler catchAssertionHandler(                                \\\n                macroName##_catch_sr, CATCH_INTERNAL_LINEINFO,                        \\\n                CATCH_INTERNAL_STRINGIFY(arg) \", \" CATCH_INTERNAL_STRINGIFY(matcher), \\\n                resultDisposition);                                                   \\\n        INTERNAL_CATCH_TRY {                                                          \\\n            catchAssertionHandler.handleExpr(                                         \\\n                    Catch::makeMatchExpr(arg, matcher, #matcher##_catch_sr));         \\\n        }                                                                             \\\n        INTERNAL_CATCH_CATCH(catchAssertionHandler)                                   \\\n        INTERNAL_CATCH_REACT(catchAssertionHandler)                                   \\\n    } while (false)\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_THROWS_MATCHES(macroName, exceptionType, resultDisposition, matcher, ...) \\\n    do {                                                                                         \\\n        Catch::AssertionHandler catchAssertionHandler(                                           \\\n                macroName##_catch_sr, CATCH_INTERNAL_LINEINFO,                                   \\\n                CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) \", \" CATCH_INTERNAL_STRINGIFY(             \\\n                        exceptionType) \", \" CATCH_INTERNAL_STRINGIFY(matcher),                   \\\n                resultDisposition);                                                              \\\n        if (catchAssertionHandler.allowThrows())                                                 \\\n            try {                                                                                \\\n                static_cast<void>(__VA_ARGS__);                                                  \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown();                      \\\n            } catch (exceptionType const &ex) {                                                  \\\n                catchAssertionHandler.handleExpr(                                                \\\n                        Catch::makeMatchExpr(ex, matcher, #matcher##_catch_sr));                 \\\n            } catch (...) {                                                                      \\\n                catchAssertionHandler.handleUnexpectedInflightException();                       \\\n            }                                                                                    \\\n        else                                                                                     \\\n            catchAssertionHandler.handleThrowingCallSkipped();                                   \\\n        INTERNAL_CATCH_REACT(catchAssertionHandler)                                              \\\n    } while (false)\n\n// end catch_capture_matchers.h\n#endif\n// start catch_generators.hpp\n\n// start catch_interfaces_generatortracker.h\n\n#include <memory>\n\nnamespace Catch {\n\nnamespace Generators {\nclass GeneratorUntypedBase {\npublic:\n    GeneratorUntypedBase() = default;\n    virtual ~GeneratorUntypedBase();\n    // Attempts to move the generator to the next element\n    //\n    // Returns true iff the move succeeded (and a valid element\n    // can be retrieved).\n    virtual bool next() = 0;\n};\nusing GeneratorBasePtr = std::unique_ptr<GeneratorUntypedBase>;\n\n}  // namespace Generators\n\nstruct IGeneratorTracker {\n    virtual ~IGeneratorTracker();\n    virtual auto hasGenerator() const -> bool = 0;\n    virtual auto getGenerator() const -> Generators::GeneratorBasePtr const & = 0;\n    virtual void setGenerator(Generators::GeneratorBasePtr &&generator) = 0;\n};\n\n}  // namespace Catch\n\n// end catch_interfaces_generatortracker.h\n// start catch_enforce.h\n\n#include <exception>\n\nnamespace Catch {\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\ntemplate <typename Ex>\n[[noreturn]] void throw_exception(Ex const &e) {\n    throw e;\n}\n#else  // ^^ Exceptions are enabled //  Exceptions are disabled vv\n[[noreturn]] void throw_exception(std::exception const &e);\n#endif\n\n[[noreturn]] void throw_logic_error(std::string const &msg);\n[[noreturn]] void throw_domain_error(std::string const &msg);\n[[noreturn]] void throw_runtime_error(std::string const &msg);\n\n}  // namespace Catch\n\n#define CATCH_MAKE_MSG(...) (Catch::ReusableStringStream() << __VA_ARGS__).str()\n\n#define CATCH_INTERNAL_ERROR(...) \\\n    Catch::throw_logic_error(     \\\n            CATCH_MAKE_MSG(CATCH_INTERNAL_LINEINFO << \": Internal Catch2 error: \" << __VA_ARGS__))\n\n#define CATCH_ERROR(...) Catch::throw_domain_error(CATCH_MAKE_MSG(__VA_ARGS__))\n\n#define CATCH_RUNTIME_ERROR(...) Catch::throw_runtime_error(CATCH_MAKE_MSG(__VA_ARGS__))\n\n#define CATCH_ENFORCE(condition, ...) \\\n    do {                              \\\n        if (!(condition))             \\\n            CATCH_ERROR(__VA_ARGS__); \\\n    } while (false)\n\n// end catch_enforce.h\n#include <cassert>\n#include <exception>\n#include <memory>\n#include <utility>\n#include <vector>\n\nnamespace Catch {\n\nclass GeneratorException : public std::exception {\n    const char *const m_msg = \"\";\n\npublic:\n    GeneratorException(const char *msg) : m_msg(msg) {}\n\n    const char *what() const noexcept override final;\n};\n\nnamespace Generators {\n\n// !TBD move this into its own location?\nnamespace pf {\ntemplate <typename T, typename... Args>\nstd::unique_ptr<T> make_unique(Args &&...args) {\n    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));\n}\n}  // namespace pf\n\ntemplate <typename T>\nstruct IGenerator : GeneratorUntypedBase {\n    virtual ~IGenerator() = default;\n\n    // Returns the current element of the generator\n    //\n    // \\Precondition The generator is either freshly constructed,\n    // or the last call to `next()` returned true\n    virtual T const &get() const = 0;\n    using type = T;\n};\n\ntemplate <typename T>\nclass SingleValueGenerator final : public IGenerator<T> {\n    T m_value;\n\npublic:\n    SingleValueGenerator(T &&value) : m_value(std::move(value)) {}\n\n    T const &get() const override { return m_value; }\n    bool next() override { return false; }\n};\n\ntemplate <typename T>\nclass FixedValuesGenerator final : public IGenerator<T> {\n    static_assert(!std::is_same<T, bool>::value,\n                  \"FixedValuesGenerator does not support bools because of std::vector<bool>\"\n                  \"specialization, use SingleValue Generator instead.\");\n    std::vector<T> m_values;\n    size_t m_idx = 0;\n\npublic:\n    FixedValuesGenerator(std::initializer_list<T> values) : m_values(values) {}\n\n    T const &get() const override { return m_values[m_idx]; }\n    bool next() override {\n        ++m_idx;\n        return m_idx < m_values.size();\n    }\n};\n\ntemplate <typename T>\nclass GeneratorWrapper final {\n    std::unique_ptr<IGenerator<T>> m_generator;\n\npublic:\n    GeneratorWrapper(std::unique_ptr<IGenerator<T>> generator)\n            : m_generator(std::move(generator)) {}\n    T const &get() const { return m_generator->get(); }\n    bool next() { return m_generator->next(); }\n};\n\ntemplate <typename T>\nGeneratorWrapper<T> value(T &&value) {\n    return GeneratorWrapper<T>(pf::make_unique<SingleValueGenerator<T>>(std::forward<T>(value)));\n}\ntemplate <typename T>\nGeneratorWrapper<T> values(std::initializer_list<T> values) {\n    return GeneratorWrapper<T>(pf::make_unique<FixedValuesGenerator<T>>(values));\n}\n\ntemplate <typename T>\nclass Generators : public IGenerator<T> {\n    std::vector<GeneratorWrapper<T>> m_generators;\n    size_t m_current = 0;\n\n    void populate(GeneratorWrapper<T> &&generator) {\n        m_generators.emplace_back(std::move(generator));\n    }\n    void populate(T &&val) { m_generators.emplace_back(value(std::forward<T>(val))); }\n    template <typename U>\n    void populate(U &&val) {\n        populate(T(std::forward<U>(val)));\n    }\n    template <typename U, typename... Gs>\n    void populate(U &&valueOrGenerator, Gs &&...moreGenerators) {\n        populate(std::forward<U>(valueOrGenerator));\n        populate(std::forward<Gs>(moreGenerators)...);\n    }\n\npublic:\n    template <typename... Gs>\n    Generators(Gs &&...moreGenerators) {\n        m_generators.reserve(sizeof...(Gs));\n        populate(std::forward<Gs>(moreGenerators)...);\n    }\n\n    T const &get() const override { return m_generators[m_current].get(); }\n\n    bool next() override {\n        if (m_current >= m_generators.size()) {\n            return false;\n        }\n        const bool current_status = m_generators[m_current].next();\n        if (!current_status) {\n            ++m_current;\n        }\n        return m_current < m_generators.size();\n    }\n};\n\ntemplate <typename... Ts>\nGeneratorWrapper<std::tuple<Ts...>> table(\n        std::initializer_list<std::tuple<typename std::decay<Ts>::type...>> tuples) {\n    return values<std::tuple<Ts...>>(tuples);\n}\n\n// Tag type to signal that a generator sequence should convert arguments to a\n// specific type\ntemplate <typename T>\nstruct as {};\n\ntemplate <typename T, typename... Gs>\nauto makeGenerators(GeneratorWrapper<T> &&generator, Gs &&...moreGenerators) -> Generators<T> {\n    return Generators<T>(std::move(generator), std::forward<Gs>(moreGenerators)...);\n}\ntemplate <typename T>\nauto makeGenerators(GeneratorWrapper<T> &&generator) -> Generators<T> {\n    return Generators<T>(std::move(generator));\n}\ntemplate <typename T, typename... Gs>\nauto makeGenerators(T &&val, Gs &&...moreGenerators) -> Generators<T> {\n    return makeGenerators(value(std::forward<T>(val)), std::forward<Gs>(moreGenerators)...);\n}\ntemplate <typename T, typename U, typename... Gs>\nauto makeGenerators(as<T>, U &&val, Gs &&...moreGenerators) -> Generators<T> {\n    return makeGenerators(value(T(std::forward<U>(val))), std::forward<Gs>(moreGenerators)...);\n}\n\nauto acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const &lineInfo)\n        -> IGeneratorTracker &;\n\ntemplate <typename L>\n// Note: The type after -> is weird, because VS2015 cannot parse\n//       the expression used in the typedef inside, when it is in\n//       return type. Yeah.\nauto generate(StringRef generatorName, SourceLineInfo const &lineInfo, L const &generatorExpression)\n        -> decltype(std::declval<decltype(generatorExpression())>().get()) {\n    using UnderlyingType = typename decltype(generatorExpression())::type;\n\n    IGeneratorTracker &tracker = acquireGeneratorTracker(generatorName, lineInfo);\n    if (!tracker.hasGenerator()) {\n        tracker.setGenerator(pf::make_unique<Generators<UnderlyingType>>(generatorExpression()));\n    }\n\n    auto const &generator =\n            static_cast<IGenerator<UnderlyingType> const &>(*tracker.getGenerator());\n    return generator.get();\n}\n\n}  // namespace Generators\n}  // namespace Catch\n\n#define GENERATE(...)                                                                            \\\n    Catch::Generators::generate(INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \\\n                                CATCH_INTERNAL_LINEINFO, [] {                                    \\\n                                    using namespace Catch::Generators;                           \\\n                                    return makeGenerators(__VA_ARGS__);                          \\\n                                })  // NOLINT(google-build-using-namespace)\n#define GENERATE_COPY(...)                                                                       \\\n    Catch::Generators::generate(INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \\\n                                CATCH_INTERNAL_LINEINFO, [=] {                                   \\\n                                    using namespace Catch::Generators;                           \\\n                                    return makeGenerators(__VA_ARGS__);                          \\\n                                })  // NOLINT(google-build-using-namespace)\n#define GENERATE_REF(...)                                                                        \\\n    Catch::Generators::generate(INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \\\n                                CATCH_INTERNAL_LINEINFO, [&] {                                   \\\n                                    using namespace Catch::Generators;                           \\\n                                    return makeGenerators(__VA_ARGS__);                          \\\n                                })  // NOLINT(google-build-using-namespace)\n\n// end catch_generators.hpp\n// start catch_generators_generic.hpp\n\nnamespace Catch {\nnamespace Generators {\n\ntemplate <typename T>\nclass TakeGenerator : public IGenerator<T> {\n    GeneratorWrapper<T> m_generator;\n    size_t m_returned = 0;\n    size_t m_target;\n\npublic:\n    TakeGenerator(size_t target, GeneratorWrapper<T> &&generator)\n            : m_generator(std::move(generator)), m_target(target) {\n        assert(target != 0 && \"Empty generators are not allowed\");\n    }\n    T const &get() const override { return m_generator.get(); }\n    bool next() override {\n        ++m_returned;\n        if (m_returned >= m_target) {\n            return false;\n        }\n\n        const auto success = m_generator.next();\n        // If the underlying generator does not contain enough values\n        // then we cut short as well\n        if (!success) {\n            m_returned = m_target;\n        }\n        return success;\n    }\n};\n\ntemplate <typename T>\nGeneratorWrapper<T> take(size_t target, GeneratorWrapper<T> &&generator) {\n    return GeneratorWrapper<T>(pf::make_unique<TakeGenerator<T>>(target, std::move(generator)));\n}\n\ntemplate <typename T, typename Predicate>\nclass FilterGenerator : public IGenerator<T> {\n    GeneratorWrapper<T> m_generator;\n    Predicate m_predicate;\n\npublic:\n    template <typename P = Predicate>\n    FilterGenerator(P &&pred, GeneratorWrapper<T> &&generator)\n            : m_generator(std::move(generator)), m_predicate(std::forward<P>(pred)) {\n        if (!m_predicate(m_generator.get())) {\n            // It might happen that there are no values that pass the\n            // filter. In that case we throw an exception.\n            auto has_initial_value = next();\n            if (!has_initial_value) {\n                Catch::throw_exception(\n                        GeneratorException(\"No valid value found in filtered generator\"));\n            }\n        }\n    }\n\n    T const &get() const override { return m_generator.get(); }\n\n    bool next() override {\n        bool success = m_generator.next();\n        if (!success) {\n            return false;\n        }\n        while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true)\n            ;\n        return success;\n    }\n};\n\ntemplate <typename T, typename Predicate>\nGeneratorWrapper<T> filter(Predicate &&pred, GeneratorWrapper<T> &&generator) {\n    return GeneratorWrapper<T>(\n            std::unique_ptr<IGenerator<T>>(pf::make_unique<FilterGenerator<T, Predicate>>(\n                    std::forward<Predicate>(pred), std::move(generator))));\n}\n\ntemplate <typename T>\nclass RepeatGenerator : public IGenerator<T> {\n    static_assert(!std::is_same<T, bool>::value,\n                  \"RepeatGenerator currently does not support bools\"\n                  \"because of std::vector<bool> specialization\");\n    GeneratorWrapper<T> m_generator;\n    mutable std::vector<T> m_returned;\n    size_t m_target_repeats;\n    size_t m_current_repeat = 0;\n    size_t m_repeat_index = 0;\n\npublic:\n    RepeatGenerator(size_t repeats, GeneratorWrapper<T> &&generator)\n            : m_generator(std::move(generator)), m_target_repeats(repeats) {\n        assert(m_target_repeats > 0 && \"Repeat generator must repeat at least once\");\n    }\n\n    T const &get() const override {\n        if (m_current_repeat == 0) {\n            m_returned.push_back(m_generator.get());\n            return m_returned.back();\n        }\n        return m_returned[m_repeat_index];\n    }\n\n    bool next() override {\n        // There are 2 basic cases:\n        // 1) We are still reading the generator\n        // 2) We are reading our own cache\n\n        // In the first case, we need to poke the underlying generator.\n        // If it happily moves, we are left in that state, otherwise it is time to\n        // start reading from our cache\n        if (m_current_repeat == 0) {\n            const auto success = m_generator.next();\n            if (!success) {\n                ++m_current_repeat;\n            }\n            return m_current_repeat < m_target_repeats;\n        }\n\n        // In the second case, we need to move indices forward and check that we\n        // haven't run up against the end\n        ++m_repeat_index;\n        if (m_repeat_index == m_returned.size()) {\n            m_repeat_index = 0;\n            ++m_current_repeat;\n        }\n        return m_current_repeat < m_target_repeats;\n    }\n};\n\ntemplate <typename T>\nGeneratorWrapper<T> repeat(size_t repeats, GeneratorWrapper<T> &&generator) {\n    return GeneratorWrapper<T>(pf::make_unique<RepeatGenerator<T>>(repeats, std::move(generator)));\n}\n\ntemplate <typename T, typename U, typename Func>\nclass MapGenerator : public IGenerator<T> {\n    // TBD: provide static assert for mapping function, for friendly error message\n    GeneratorWrapper<U> m_generator;\n    Func m_function;\n    // To avoid returning dangling reference, we have to save the values\n    T m_cache;\n\npublic:\n    template <typename F2 = Func>\n    MapGenerator(F2 &&function, GeneratorWrapper<U> &&generator)\n            : m_generator(std::move(generator)),\n              m_function(std::forward<F2>(function)),\n              m_cache(m_function(m_generator.get())) {}\n\n    T const &get() const override { return m_cache; }\n    bool next() override {\n        const auto success = m_generator.next();\n        if (success) {\n            m_cache = m_function(m_generator.get());\n        }\n        return success;\n    }\n};\n\ntemplate <typename Func, typename U, typename T = FunctionReturnType<Func, U>>\nGeneratorWrapper<T> map(Func &&function, GeneratorWrapper<U> &&generator) {\n    return GeneratorWrapper<T>(pf::make_unique<MapGenerator<T, U, Func>>(\n            std::forward<Func>(function), std::move(generator)));\n}\n\ntemplate <typename T, typename U, typename Func>\nGeneratorWrapper<T> map(Func &&function, GeneratorWrapper<U> &&generator) {\n    return GeneratorWrapper<T>(pf::make_unique<MapGenerator<T, U, Func>>(\n            std::forward<Func>(function), std::move(generator)));\n}\n\ntemplate <typename T>\nclass ChunkGenerator final : public IGenerator<std::vector<T>> {\n    std::vector<T> m_chunk;\n    size_t m_chunk_size;\n    GeneratorWrapper<T> m_generator;\n    bool m_used_up = false;\n\npublic:\n    ChunkGenerator(size_t size, GeneratorWrapper<T> generator)\n            : m_chunk_size(size), m_generator(std::move(generator)) {\n        m_chunk.reserve(m_chunk_size);\n        if (m_chunk_size != 0) {\n            m_chunk.push_back(m_generator.get());\n            for (size_t i = 1; i < m_chunk_size; ++i) {\n                if (!m_generator.next()) {\n                    Catch::throw_exception(\n                            GeneratorException(\"Not enough values to initialize the first chunk\"));\n                }\n                m_chunk.push_back(m_generator.get());\n            }\n        }\n    }\n    std::vector<T> const &get() const override { return m_chunk; }\n    bool next() override {\n        m_chunk.clear();\n        for (size_t idx = 0; idx < m_chunk_size; ++idx) {\n            if (!m_generator.next()) {\n                return false;\n            }\n            m_chunk.push_back(m_generator.get());\n        }\n        return true;\n    }\n};\n\ntemplate <typename T>\nGeneratorWrapper<std::vector<T>> chunk(size_t size, GeneratorWrapper<T> &&generator) {\n    return GeneratorWrapper<std::vector<T>>(\n            pf::make_unique<ChunkGenerator<T>>(size, std::move(generator)));\n}\n\n}  // namespace Generators\n}  // namespace Catch\n\n// end catch_generators_generic.hpp\n// start catch_generators_specific.hpp\n\n// start catch_context.h\n\n#include <memory>\n\nnamespace Catch {\n\nstruct IResultCapture;\nstruct IRunner;\nstruct IConfig;\nstruct IMutableContext;\n\nusing IConfigPtr = std::shared_ptr<IConfig const>;\n\nstruct IContext {\n    virtual ~IContext();\n\n    virtual IResultCapture *getResultCapture() = 0;\n    virtual IRunner *getRunner() = 0;\n    virtual IConfigPtr const &getConfig() const = 0;\n};\n\nstruct IMutableContext : IContext {\n    virtual ~IMutableContext();\n    virtual void setResultCapture(IResultCapture *resultCapture) = 0;\n    virtual void setRunner(IRunner *runner) = 0;\n    virtual void setConfig(IConfigPtr const &config) = 0;\n\nprivate:\n    static IMutableContext *currentContext;\n    friend IMutableContext &getCurrentMutableContext();\n    friend void cleanUpContext();\n    static void createContext();\n};\n\ninline IMutableContext &getCurrentMutableContext() {\n    if (!IMutableContext::currentContext)\n        IMutableContext::createContext();\n    // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)\n    return *IMutableContext::currentContext;\n}\n\ninline IContext &getCurrentContext() { return getCurrentMutableContext(); }\n\nvoid cleanUpContext();\n\nclass SimplePcg32;\nSimplePcg32 &rng();\n}  // namespace Catch\n\n// end catch_context.h\n// start catch_interfaces_config.h\n\n// start catch_option.hpp\n\nnamespace Catch {\n\n// An optional type\ntemplate <typename T>\nclass Option {\npublic:\n    Option() : nullableValue(nullptr) {}\n    Option(T const &_value) : nullableValue(new (storage) T(_value)) {}\n    Option(Option const &_other) : nullableValue(_other ? new (storage) T(*_other) : nullptr) {}\n\n    ~Option() { reset(); }\n\n    Option &operator=(Option const &_other) {\n        if (&_other != this) {\n            reset();\n            if (_other)\n                nullableValue = new (storage) T(*_other);\n        }\n        return *this;\n    }\n    Option &operator=(T const &_value) {\n        reset();\n        nullableValue = new (storage) T(_value);\n        return *this;\n    }\n\n    void reset() {\n        if (nullableValue)\n            nullableValue->~T();\n        nullableValue = nullptr;\n    }\n\n    T &operator*() { return *nullableValue; }\n    T const &operator*() const { return *nullableValue; }\n    T *operator->() { return nullableValue; }\n    const T *operator->() const { return nullableValue; }\n\n    T valueOr(T const &defaultValue) const { return nullableValue ? *nullableValue : defaultValue; }\n\n    bool some() const { return nullableValue != nullptr; }\n    bool none() const { return nullableValue == nullptr; }\n\n    bool operator!() const { return nullableValue == nullptr; }\n    explicit operator bool() const { return some(); }\n\nprivate:\n    T *nullableValue;\n    alignas(alignof(T)) char storage[sizeof(T)];\n};\n\n}  // end namespace Catch\n\n// end catch_option.hpp\n#include <chrono>\n#include <iosfwd>\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\nenum class Verbosity { Quiet = 0, Normal, High };\n\nstruct WarnAbout {\n    enum What { Nothing = 0x00, NoAssertions = 0x01, NoTests = 0x02 };\n};\n\nstruct ShowDurations {\n    enum OrNot { DefaultForReporter, Always, Never };\n};\nstruct RunTests {\n    enum InWhatOrder { InDeclarationOrder, InLexicographicalOrder, InRandomOrder };\n};\nstruct UseColour {\n    enum YesOrNo { Auto, Yes, No };\n};\nstruct WaitForKeypress {\n    enum When {\n        Never,\n        BeforeStart = 1,\n        BeforeExit = 2,\n        BeforeStartAndExit = BeforeStart | BeforeExit\n    };\n};\n\nclass TestSpec;\n\nstruct IConfig : NonCopyable {\n    virtual ~IConfig();\n\n    virtual bool allowThrows() const = 0;\n    virtual std::ostream &stream() const = 0;\n    virtual std::string name() const = 0;\n    virtual bool includeSuccessfulResults() const = 0;\n    virtual bool shouldDebugBreak() const = 0;\n    virtual bool warnAboutMissingAssertions() const = 0;\n    virtual bool warnAboutNoTests() const = 0;\n    virtual int abortAfter() const = 0;\n    virtual bool showInvisibles() const = 0;\n    virtual ShowDurations::OrNot showDurations() const = 0;\n    virtual double minDuration() const = 0;\n    virtual TestSpec const &testSpec() const = 0;\n    virtual bool hasTestFilters() const = 0;\n    virtual std::vector<std::string> const &getTestsOrTags() const = 0;\n    virtual RunTests::InWhatOrder runOrder() const = 0;\n    virtual unsigned int rngSeed() const = 0;\n    virtual UseColour::YesOrNo useColour() const = 0;\n    virtual std::vector<std::string> const &getSectionsToRun() const = 0;\n    virtual Verbosity verbosity() const = 0;\n\n    virtual bool benchmarkNoAnalysis() const = 0;\n    virtual int benchmarkSamples() const = 0;\n    virtual double benchmarkConfidenceInterval() const = 0;\n    virtual unsigned int benchmarkResamples() const = 0;\n    virtual std::chrono::milliseconds benchmarkWarmupTime() const = 0;\n};\n\nusing IConfigPtr = std::shared_ptr<IConfig const>;\n}  // namespace Catch\n\n// end catch_interfaces_config.h\n// start catch_random_number_generator.h\n\n#include <cstdint>\n\nnamespace Catch {\n\n// This is a simple implementation of C++11 Uniform Random Number\n// Generator. It does not provide all operators, because Catch2\n// does not use it, but it should behave as expected inside stdlib's\n// distributions.\n// The implementation is based on the PCG family (http://pcg-random.org)\nclass SimplePcg32 {\n    using state_type = std::uint64_t;\n\npublic:\n    using result_type = std::uint32_t;\n    static constexpr result_type(min)() { return 0; }\n    static constexpr result_type(max)() { return static_cast<result_type>(-1); }\n\n    // Provide some default initial state for the default constructor\n    SimplePcg32() : SimplePcg32(0xed743cc4U) {}\n\n    explicit SimplePcg32(result_type seed_);\n\n    void seed(result_type seed_);\n    void discard(uint64_t skip);\n\n    result_type operator()();\n\nprivate:\n    friend bool operator==(SimplePcg32 const &lhs, SimplePcg32 const &rhs);\n    friend bool operator!=(SimplePcg32 const &lhs, SimplePcg32 const &rhs);\n\n    // In theory we also need operator<< and operator>>\n    // In practice we do not use them, so we will skip them for now\n\n    std::uint64_t m_state;\n    // This part of the state determines which \"stream\" of the numbers\n    // is chosen -- we take it as a constant for Catch2, so we only\n    // need to deal with seeding the main state.\n    // Picked by reading 8 bytes from `/dev/random` :-)\n    static const std::uint64_t s_inc = (0x13ed0cc53f939476ULL << 1ULL) | 1ULL;\n};\n\n}  // end namespace Catch\n\n// end catch_random_number_generator.h\n#include <random>\n\nnamespace Catch {\nnamespace Generators {\n\ntemplate <typename Float>\nclass RandomFloatingGenerator final : public IGenerator<Float> {\n    Catch::SimplePcg32 &m_rng;\n    std::uniform_real_distribution<Float> m_dist;\n    Float m_current_number;\n\npublic:\n    RandomFloatingGenerator(Float a, Float b) : m_rng(rng()), m_dist(a, b) {\n        static_cast<void>(next());\n    }\n\n    Float const &get() const override { return m_current_number; }\n    bool next() override {\n        m_current_number = m_dist(m_rng);\n        return true;\n    }\n};\n\ntemplate <typename Integer>\nclass RandomIntegerGenerator final : public IGenerator<Integer> {\n    Catch::SimplePcg32 &m_rng;\n    std::uniform_int_distribution<Integer> m_dist;\n    Integer m_current_number;\n\npublic:\n    RandomIntegerGenerator(Integer a, Integer b) : m_rng(rng()), m_dist(a, b) {\n        static_cast<void>(next());\n    }\n\n    Integer const &get() const override { return m_current_number; }\n    bool next() override {\n        m_current_number = m_dist(m_rng);\n        return true;\n    }\n};\n\n// TODO: Ideally this would be also constrained against the various char types,\n//       but I don't expect users to run into that in practice.\ntemplate <typename T>\ntypename std::enable_if<std::is_integral<T>::value && !std::is_same<T, bool>::value,\n                        GeneratorWrapper<T>>::type\nrandom(T a, T b) {\n    return GeneratorWrapper<T>(pf::make_unique<RandomIntegerGenerator<T>>(a, b));\n}\n\ntemplate <typename T>\ntypename std::enable_if<std::is_floating_point<T>::value, GeneratorWrapper<T>>::type random(T a,\n                                                                                            T b) {\n    return GeneratorWrapper<T>(pf::make_unique<RandomFloatingGenerator<T>>(a, b));\n}\n\ntemplate <typename T>\nclass RangeGenerator final : public IGenerator<T> {\n    T m_current;\n    T m_end;\n    T m_step;\n    bool m_positive;\n\npublic:\n    RangeGenerator(T const &start, T const &end, T const &step)\n            : m_current(start), m_end(end), m_step(step), m_positive(m_step > T(0)) {\n        assert(m_current != m_end && \"Range start and end cannot be equal\");\n        assert(m_step != T(0) && \"Step size cannot be zero\");\n        assert(((m_positive && m_current <= m_end) || (!m_positive && m_current >= m_end)) &&\n               \"Step moves away from end\");\n    }\n\n    RangeGenerator(T const &start, T const &end)\n            : RangeGenerator(start, end, (start < end) ? T(1) : T(-1)) {}\n\n    T const &get() const override { return m_current; }\n\n    bool next() override {\n        m_current += m_step;\n        return (m_positive) ? (m_current < m_end) : (m_current > m_end);\n    }\n};\n\ntemplate <typename T>\nGeneratorWrapper<T> range(T const &start, T const &end, T const &step) {\n    static_assert(std::is_arithmetic<T>::value && !std::is_same<T, bool>::value,\n                  \"Type must be numeric\");\n    return GeneratorWrapper<T>(pf::make_unique<RangeGenerator<T>>(start, end, step));\n}\n\ntemplate <typename T>\nGeneratorWrapper<T> range(T const &start, T const &end) {\n    static_assert(std::is_integral<T>::value && !std::is_same<T, bool>::value,\n                  \"Type must be an integer\");\n    return GeneratorWrapper<T>(pf::make_unique<RangeGenerator<T>>(start, end));\n}\n\ntemplate <typename T>\nclass IteratorGenerator final : public IGenerator<T> {\n    static_assert(!std::is_same<T, bool>::value,\n                  \"IteratorGenerator currently does not support bools\"\n                  \"because of std::vector<bool> specialization\");\n\n    std::vector<T> m_elems;\n    size_t m_current = 0;\n\npublic:\n    template <typename InputIterator, typename InputSentinel>\n    IteratorGenerator(InputIterator first, InputSentinel last) : m_elems(first, last) {\n        if (m_elems.empty()) {\n            Catch::throw_exception(\n                    GeneratorException(\"IteratorGenerator received no valid values\"));\n        }\n    }\n\n    T const &get() const override { return m_elems[m_current]; }\n\n    bool next() override {\n        ++m_current;\n        return m_current != m_elems.size();\n    }\n};\n\ntemplate <typename InputIterator,\n          typename InputSentinel,\n          typename ResultType = typename std::iterator_traits<InputIterator>::value_type>\nGeneratorWrapper<ResultType> from_range(InputIterator from, InputSentinel to) {\n    return GeneratorWrapper<ResultType>(pf::make_unique<IteratorGenerator<ResultType>>(from, to));\n}\n\ntemplate <typename Container, typename ResultType = typename Container::value_type>\nGeneratorWrapper<ResultType> from_range(Container const &cnt) {\n    return GeneratorWrapper<ResultType>(\n            pf::make_unique<IteratorGenerator<ResultType>>(cnt.begin(), cnt.end()));\n}\n\n}  // namespace Generators\n}  // namespace Catch\n\n// end catch_generators_specific.hpp\n\n// These files are included here so the single_include script doesn't put them\n// in the conditionally compiled sections\n// start catch_test_case_info.h\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpadded\"\n#endif\n\nnamespace Catch {\n\nstruct ITestInvoker;\n\nstruct TestCaseInfo {\n    enum SpecialProperties {\n        None = 0,\n        IsHidden = 1 << 1,\n        ShouldFail = 1 << 2,\n        MayFail = 1 << 3,\n        Throws = 1 << 4,\n        NonPortable = 1 << 5,\n        Benchmark = 1 << 6\n    };\n\n    TestCaseInfo(std::string const &_name,\n                 std::string const &_className,\n                 std::string const &_description,\n                 std::vector<std::string> const &_tags,\n                 SourceLineInfo const &_lineInfo);\n\n    friend void setTags(TestCaseInfo &testCaseInfo, std::vector<std::string> tags);\n\n    bool isHidden() const;\n    bool throws() const;\n    bool okToFail() const;\n    bool expectedToFail() const;\n\n    std::string tagsAsString() const;\n\n    std::string name;\n    std::string className;\n    std::string description;\n    std::vector<std::string> tags;\n    std::vector<std::string> lcaseTags;\n    SourceLineInfo lineInfo;\n    SpecialProperties properties;\n};\n\nclass TestCase : public TestCaseInfo {\npublic:\n    TestCase(ITestInvoker *testCase, TestCaseInfo &&info);\n\n    TestCase withName(std::string const &_newName) const;\n\n    void invoke() const;\n\n    TestCaseInfo const &getTestCaseInfo() const;\n\n    bool operator==(TestCase const &other) const;\n    bool operator<(TestCase const &other) const;\n\nprivate:\n    std::shared_ptr<ITestInvoker> test;\n};\n\nTestCase makeTestCase(ITestInvoker *testCase,\n                      std::string const &className,\n                      NameAndTags const &nameAndTags,\n                      SourceLineInfo const &lineInfo);\n}  // namespace Catch\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n// end catch_test_case_info.h\n// start catch_interfaces_runner.h\n\nnamespace Catch {\n\nstruct IRunner {\n    virtual ~IRunner();\n    virtual bool aborting() const = 0;\n};\n}  // namespace Catch\n\n// end catch_interfaces_runner.h\n\n#ifdef __OBJC__\n// start catch_objc.hpp\n\n#import <objc/runtime.h>\n\n#include <string>\n\n// NB. Any general catch headers included here must be included\n// in catch.hpp first to make sure they are included by the single\n// header for non obj-usage\n\n///////////////////////////////////////////////////////////////////////////////\n// This protocol is really only here for (self) documenting purposes, since\n// all its methods are optional.\n@protocol OcFixture\n\n@optional\n\n- (void)setUp;\n- (void)tearDown;\n\n@end\n\nnamespace Catch {\n\nclass OcMethod : public ITestInvoker {\npublic:\n    OcMethod(Class cls, SEL sel) : m_cls(cls), m_sel(sel) {}\n\n    virtual void invoke() const {\n        id obj = [[m_cls alloc] init];\n\n        performOptionalSelector(obj, @selector(setUp));\n        performOptionalSelector(obj, m_sel);\n        performOptionalSelector(obj, @selector(tearDown));\n\n        arcSafeRelease(obj);\n    }\n\nprivate:\n    virtual ~OcMethod() {}\n\n    Class m_cls;\n    SEL m_sel;\n};\n\nnamespace Detail {\n\ninline std::string getAnnotation(Class cls,\n                                 std::string const &annotationName,\n                                 std::string const &testCaseName) {\n    NSString *selStr = [[NSString alloc]\n            initWithFormat:@\"Catch_%s_%s\", annotationName.c_str(), testCaseName.c_str()];\n    SEL sel = NSSelectorFromString(selStr);\n    arcSafeRelease(selStr);\n    id value = performOptionalSelector(cls, sel);\n    if (value)\n        return [(NSString *)value UTF8String];\n    return \"\";\n}\n}  // namespace Detail\n\ninline std::size_t registerTestMethods() {\n    std::size_t noTestMethods = 0;\n    int noClasses = objc_getClassList(nullptr, 0);\n\n    Class *classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc(sizeof(Class) * noClasses);\n    objc_getClassList(classes, noClasses);\n\n    for (int c = 0; c < noClasses; c++) {\n        Class cls = classes[c];\n        {\n            u_int count;\n            Method *methods = class_copyMethodList(cls, &count);\n            for (u_int m = 0; m < count; m++) {\n                SEL selector = method_getName(methods[m]);\n                std::string methodName = sel_getName(selector);\n                if (startsWith(methodName, \"Catch_TestCase_\")) {\n                    std::string testCaseName = methodName.substr(15);\n                    std::string name = Detail::getAnnotation(cls, \"Name\", testCaseName);\n                    std::string desc = Detail::getAnnotation(cls, \"Description\", testCaseName);\n                    const char *className = class_getName(cls);\n\n                    getMutableRegistryHub().registerTest(makeTestCase(\n                            new OcMethod(cls, selector), className,\n                            NameAndTags(name.c_str(), desc.c_str()), SourceLineInfo(\"\", 0)));\n                    noTestMethods++;\n                }\n            }\n            free(methods);\n        }\n    }\n    return noTestMethods;\n}\n\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n\nnamespace Matchers {\nnamespace Impl {\nnamespace NSStringMatchers {\n\nstruct StringHolder : MatcherBase<NSString *> {\n    StringHolder(NSString *substr) : m_substr([substr copy]) {}\n    StringHolder(StringHolder const &other) : m_substr([other.m_substr copy]) {}\n    StringHolder() { arcSafeRelease(m_substr); }\n\n    bool match(NSString *str) const override { return false; }\n\n    NSString *CATCH_ARC_STRONG m_substr;\n};\n\nstruct Equals : StringHolder {\n    Equals(NSString *substr) : StringHolder(substr) {}\n\n    bool match(NSString *str) const override {\n        return (str != nil || m_substr == nil) && [str isEqualToString:m_substr];\n    }\n\n    std::string describe() const override {\n        return \"equals string: \" + Catch::Detail::stringify(m_substr);\n    }\n};\n\nstruct Contains : StringHolder {\n    Contains(NSString *substr) : StringHolder(substr) {}\n\n    bool match(NSString *str) const override {\n        return (str != nil || m_substr == nil) &&\n               [str rangeOfString:m_substr].location != NSNotFound;\n    }\n\n    std::string describe() const override {\n        return \"contains string: \" + Catch::Detail::stringify(m_substr);\n    }\n};\n\nstruct StartsWith : StringHolder {\n    StartsWith(NSString *substr) : StringHolder(substr) {}\n\n    bool match(NSString *str) const override {\n        return (str != nil || m_substr == nil) && [str rangeOfString:m_substr].location == 0;\n    }\n\n    std::string describe() const override {\n        return \"starts with: \" + Catch::Detail::stringify(m_substr);\n    }\n};\nstruct EndsWith : StringHolder {\n    EndsWith(NSString *substr) : StringHolder(substr) {}\n\n    bool match(NSString *str) const override {\n        return (str != nil || m_substr == nil) &&\n               [str rangeOfString:m_substr].location == [str length] - [m_substr length];\n    }\n\n    std::string describe() const override {\n        return \"ends with: \" + Catch::Detail::stringify(m_substr);\n    }\n};\n\n}  // namespace NSStringMatchers\n}  // namespace Impl\n\ninline Impl::NSStringMatchers::Equals Equals(NSString *substr) {\n    return Impl::NSStringMatchers::Equals(substr);\n}\n\ninline Impl::NSStringMatchers::Contains Contains(NSString *substr) {\n    return Impl::NSStringMatchers::Contains(substr);\n}\n\ninline Impl::NSStringMatchers::StartsWith StartsWith(NSString *substr) {\n    return Impl::NSStringMatchers::StartsWith(substr);\n}\n\ninline Impl::NSStringMatchers::EndsWith EndsWith(NSString *substr) {\n    return Impl::NSStringMatchers::EndsWith(substr);\n}\n\n}  // namespace Matchers\n\nusing namespace Matchers;\n\n#endif  // CATCH_CONFIG_DISABLE_MATCHERS\n\n}  // namespace Catch\n\n///////////////////////////////////////////////////////////////////////////////\n#define OC_MAKE_UNIQUE_NAME(root, uniqueSuffix) root##uniqueSuffix\n#define OC_TEST_CASE2(name, desc, uniqueSuffix)                               \\\n    +(NSString *)OC_MAKE_UNIQUE_NAME(Catch_Name_test_, uniqueSuffix) {        \\\n        return @name;                                                         \\\n    }                                                                         \\\n    +(NSString *)OC_MAKE_UNIQUE_NAME(Catch_Description_test_, uniqueSuffix) { \\\n        return @desc;                                                         \\\n    }                                                                         \\\n    -(void)OC_MAKE_UNIQUE_NAME(Catch_TestCase_test_, uniqueSuffix)\n\n#define OC_TEST_CASE(name, desc) OC_TEST_CASE2(name, desc, __LINE__)\n\n// end catch_objc.hpp\n#endif\n\n// Benchmarking needs the externally-facing parts of reporters to work\n#if defined(CATCH_CONFIG_EXTERNAL_INTERFACES) || defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n// start catch_external_interfaces.h\n\n// start catch_reporter_bases.hpp\n\n// start catch_interfaces_reporter.h\n\n// start catch_config.hpp\n\n// start catch_test_spec_parser.h\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpadded\"\n#endif\n\n// start catch_test_spec.h\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpadded\"\n#endif\n\n// start catch_wildcard_pattern.h\n\nnamespace Catch {\nclass WildcardPattern {\n    enum WildcardPosition {\n        NoWildcard = 0,\n        WildcardAtStart = 1,\n        WildcardAtEnd = 2,\n        WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd\n    };\n\npublic:\n    WildcardPattern(std::string const &pattern, CaseSensitive::Choice caseSensitivity);\n    virtual ~WildcardPattern() = default;\n    virtual bool matches(std::string const &str) const;\n\nprivate:\n    std::string normaliseString(std::string const &str) const;\n    CaseSensitive::Choice m_caseSensitivity;\n    WildcardPosition m_wildcard = NoWildcard;\n    std::string m_pattern;\n};\n}  // namespace Catch\n\n// end catch_wildcard_pattern.h\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\nstruct IConfig;\n\nclass TestSpec {\n    class Pattern {\n    public:\n        explicit Pattern(std::string const &name);\n        virtual ~Pattern();\n        virtual bool matches(TestCaseInfo const &testCase) const = 0;\n        std::string const &name() const;\n\n    private:\n        std::string const m_name;\n    };\n    using PatternPtr = std::shared_ptr<Pattern>;\n\n    class NamePattern : public Pattern {\n    public:\n        explicit NamePattern(std::string const &name, std::string const &filterString);\n        bool matches(TestCaseInfo const &testCase) const override;\n\n    private:\n        WildcardPattern m_wildcardPattern;\n    };\n\n    class TagPattern : public Pattern {\n    public:\n        explicit TagPattern(std::string const &tag, std::string const &filterString);\n        bool matches(TestCaseInfo const &testCase) const override;\n\n    private:\n        std::string m_tag;\n    };\n\n    class ExcludedPattern : public Pattern {\n    public:\n        explicit ExcludedPattern(PatternPtr const &underlyingPattern);\n        bool matches(TestCaseInfo const &testCase) const override;\n\n    private:\n        PatternPtr m_underlyingPattern;\n    };\n\n    struct Filter {\n        std::vector<PatternPtr> m_patterns;\n\n        bool matches(TestCaseInfo const &testCase) const;\n        std::string name() const;\n    };\n\npublic:\n    struct FilterMatch {\n        std::string name;\n        std::vector<TestCase const *> tests;\n    };\n    using Matches = std::vector<FilterMatch>;\n    using vectorStrings = std::vector<std::string>;\n\n    bool hasFilters() const;\n    bool matches(TestCaseInfo const &testCase) const;\n    Matches matchesByFilter(std::vector<TestCase> const &testCases, IConfig const &config) const;\n    const vectorStrings &getInvalidArgs() const;\n\nprivate:\n    std::vector<Filter> m_filters;\n    std::vector<std::string> m_invalidArgs;\n    friend class TestSpecParser;\n};\n}  // namespace Catch\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n// end catch_test_spec.h\n// start catch_interfaces_tag_alias_registry.h\n\n#include <string>\n\nnamespace Catch {\n\nstruct TagAlias;\n\nstruct ITagAliasRegistry {\n    virtual ~ITagAliasRegistry();\n    // Nullptr if not present\n    virtual TagAlias const *find(std::string const &alias) const = 0;\n    virtual std::string expandAliases(std::string const &unexpandedTestSpec) const = 0;\n\n    static ITagAliasRegistry const &get();\n};\n\n}  // end namespace Catch\n\n// end catch_interfaces_tag_alias_registry.h\nnamespace Catch {\n\nclass TestSpecParser {\n    enum Mode { None, Name, QuotedName, Tag, EscapedName };\n    Mode m_mode = None;\n    Mode lastMode = None;\n    bool m_exclusion = false;\n    std::size_t m_pos = 0;\n    std::size_t m_realPatternPos = 0;\n    std::string m_arg;\n    std::string m_substring;\n    std::string m_patternName;\n    std::vector<std::size_t> m_escapeChars;\n    TestSpec::Filter m_currentFilter;\n    TestSpec m_testSpec;\n    ITagAliasRegistry const *m_tagAliases = nullptr;\n\npublic:\n    TestSpecParser(ITagAliasRegistry const &tagAliases);\n\n    TestSpecParser &parse(std::string const &arg);\n    TestSpec testSpec();\n\nprivate:\n    bool visitChar(char c);\n    void startNewMode(Mode mode);\n    bool processNoneChar(char c);\n    void processNameChar(char c);\n    bool processOtherChar(char c);\n    void endMode();\n    void escape();\n    bool isControlChar(char c) const;\n    void saveLastMode();\n    void revertBackToLastMode();\n    void addFilter();\n    bool separate();\n\n    // Handles common preprocessing of the pattern for name/tag patterns\n    std::string preprocessPattern();\n    // Adds the current pattern as a test name\n    void addNamePattern();\n    // Adds the current pattern as a tag\n    void addTagPattern();\n\n    inline void addCharToPattern(char c) {\n        m_substring += c;\n        m_patternName += c;\n        m_realPatternPos++;\n    }\n};\nTestSpec parseTestSpec(std::string const &arg);\n\n}  // namespace Catch\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n// end catch_test_spec_parser.h\n// Libstdc++ doesn't like incomplete classes for unique_ptr\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#ifndef CATCH_CONFIG_CONSOLE_WIDTH\n#define CATCH_CONFIG_CONSOLE_WIDTH 80\n#endif\n\nnamespace Catch {\n\nstruct IStream;\n\nstruct ConfigData {\n    bool listTests = false;\n    bool listTags = false;\n    bool listReporters = false;\n    bool listTestNamesOnly = false;\n\n    bool showSuccessfulTests = false;\n    bool shouldDebugBreak = false;\n    bool noThrow = false;\n    bool showHelp = false;\n    bool showInvisibles = false;\n    bool filenamesAsTags = false;\n    bool libIdentify = false;\n\n    int abortAfter = -1;\n    unsigned int rngSeed = 0;\n\n    bool benchmarkNoAnalysis = false;\n    unsigned int benchmarkSamples = 100;\n    double benchmarkConfidenceInterval = 0.95;\n    unsigned int benchmarkResamples = 100000;\n    std::chrono::milliseconds::rep benchmarkWarmupTime = 100;\n\n    Verbosity verbosity = Verbosity::Normal;\n    WarnAbout::What warnings = WarnAbout::Nothing;\n    ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter;\n    double minDuration = -1;\n    RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder;\n    UseColour::YesOrNo useColour = UseColour::Auto;\n    WaitForKeypress::When waitForKeypress = WaitForKeypress::Never;\n\n    std::string outputFilename;\n    std::string name;\n    std::string processName;\n#ifndef CATCH_CONFIG_DEFAULT_REPORTER\n#define CATCH_CONFIG_DEFAULT_REPORTER \"console\"\n#endif\n    std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER;\n#undef CATCH_CONFIG_DEFAULT_REPORTER\n\n    std::vector<std::string> testsOrTags;\n    std::vector<std::string> sectionsToRun;\n};\n\nclass Config : public IConfig {\npublic:\n    Config() = default;\n    Config(ConfigData const &data);\n    virtual ~Config() = default;\n\n    std::string const &getFilename() const;\n\n    bool listTests() const;\n    bool listTestNamesOnly() const;\n    bool listTags() const;\n    bool listReporters() const;\n\n    std::string getProcessName() const;\n    std::string const &getReporterName() const;\n\n    std::vector<std::string> const &getTestsOrTags() const override;\n    std::vector<std::string> const &getSectionsToRun() const override;\n\n    TestSpec const &testSpec() const override;\n    bool hasTestFilters() const override;\n\n    bool showHelp() const;\n\n    // IConfig interface\n    bool allowThrows() const override;\n    std::ostream &stream() const override;\n    std::string name() const override;\n    bool includeSuccessfulResults() const override;\n    bool warnAboutMissingAssertions() const override;\n    bool warnAboutNoTests() const override;\n    ShowDurations::OrNot showDurations() const override;\n    double minDuration() const override;\n    RunTests::InWhatOrder runOrder() const override;\n    unsigned int rngSeed() const override;\n    UseColour::YesOrNo useColour() const override;\n    bool shouldDebugBreak() const override;\n    int abortAfter() const override;\n    bool showInvisibles() const override;\n    Verbosity verbosity() const override;\n    bool benchmarkNoAnalysis() const override;\n    int benchmarkSamples() const override;\n    double benchmarkConfidenceInterval() const override;\n    unsigned int benchmarkResamples() const override;\n    std::chrono::milliseconds benchmarkWarmupTime() const override;\n\nprivate:\n    IStream const *openStream();\n    ConfigData m_data;\n\n    std::unique_ptr<IStream const> m_stream;\n    TestSpec m_testSpec;\n    bool m_hasTestFilters = false;\n};\n\n}  // end namespace Catch\n\n// end catch_config.hpp\n// start catch_assertionresult.h\n\n#include <string>\n\nnamespace Catch {\n\nstruct AssertionResultData {\n    AssertionResultData() = delete;\n\n    AssertionResultData(ResultWas::OfType _resultType, LazyExpression const &_lazyExpression);\n\n    std::string message;\n    mutable std::string reconstructedExpression;\n    LazyExpression lazyExpression;\n    ResultWas::OfType resultType;\n\n    std::string reconstructExpression() const;\n};\n\nclass AssertionResult {\npublic:\n    AssertionResult() = delete;\n    AssertionResult(AssertionInfo const &info, AssertionResultData const &data);\n\n    bool isOk() const;\n    bool succeeded() const;\n    ResultWas::OfType getResultType() const;\n    bool hasExpression() const;\n    bool hasMessage() const;\n    std::string getExpression() const;\n    std::string getExpressionInMacro() const;\n    bool hasExpandedExpression() const;\n    std::string getExpandedExpression() const;\n    std::string getMessage() const;\n    SourceLineInfo getSourceInfo() const;\n    StringRef getTestMacroName() const;\n\n    // protected:\n    AssertionInfo m_info;\n    AssertionResultData m_resultData;\n};\n\n}  // end namespace Catch\n\n// end catch_assertionresult.h\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n// start catch_estimate.hpp\n\n// Statistics estimates\n\nnamespace Catch {\nnamespace Benchmark {\ntemplate <typename Duration>\nstruct Estimate {\n    Duration point;\n    Duration lower_bound;\n    Duration upper_bound;\n    double confidence_interval;\n\n    template <typename Duration2>\n    operator Estimate<Duration2>() const {\n        return {point, lower_bound, upper_bound, confidence_interval};\n    }\n};\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_estimate.hpp\n// start catch_outlier_classification.hpp\n\n// Outlier information\n\nnamespace Catch {\nnamespace Benchmark {\nstruct OutlierClassification {\n    int samples_seen = 0;\n    int low_severe = 0;   // more than 3 times IQR below Q1\n    int low_mild = 0;     // 1.5 to 3 times IQR below Q1\n    int high_mild = 0;    // 1.5 to 3 times IQR above Q3\n    int high_severe = 0;  // more than 3 times IQR above Q3\n\n    int total() const { return low_severe + low_mild + high_mild + high_severe; }\n};\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_outlier_classification.hpp\n\n#include <iterator>\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n#include <algorithm>\n#include <iosfwd>\n#include <map>\n#include <memory>\n#include <set>\n#include <string>\n\nnamespace Catch {\n\nstruct ReporterConfig {\n    explicit ReporterConfig(IConfigPtr const &_fullConfig);\n\n    ReporterConfig(IConfigPtr const &_fullConfig, std::ostream &_stream);\n\n    std::ostream &stream() const;\n    IConfigPtr fullConfig() const;\n\nprivate:\n    std::ostream *m_stream;\n    IConfigPtr m_fullConfig;\n};\n\nstruct ReporterPreferences {\n    bool shouldRedirectStdOut = false;\n    bool shouldReportAllAssertions = false;\n};\n\ntemplate <typename T>\nstruct LazyStat : Option<T> {\n    LazyStat &operator=(T const &_value) {\n        Option<T>::operator=(_value);\n        used = false;\n        return *this;\n    }\n    void reset() {\n        Option<T>::reset();\n        used = false;\n    }\n    bool used = false;\n};\n\nstruct TestRunInfo {\n    TestRunInfo(std::string const &_name);\n    std::string name;\n};\nstruct GroupInfo {\n    GroupInfo(std::string const &_name, std::size_t _groupIndex, std::size_t _groupsCount);\n\n    std::string name;\n    std::size_t groupIndex;\n    std::size_t groupsCounts;\n};\n\nstruct AssertionStats {\n    AssertionStats(AssertionResult const &_assertionResult,\n                   std::vector<MessageInfo> const &_infoMessages,\n                   Totals const &_totals);\n\n    AssertionStats(AssertionStats const &) = default;\n    AssertionStats(AssertionStats &&) = default;\n    AssertionStats &operator=(AssertionStats const &) = delete;\n    AssertionStats &operator=(AssertionStats &&) = delete;\n    virtual ~AssertionStats();\n\n    AssertionResult assertionResult;\n    std::vector<MessageInfo> infoMessages;\n    Totals totals;\n};\n\nstruct SectionStats {\n    SectionStats(SectionInfo const &_sectionInfo,\n                 Counts const &_assertions,\n                 double _durationInSeconds,\n                 bool _missingAssertions);\n    SectionStats(SectionStats const &) = default;\n    SectionStats(SectionStats &&) = default;\n    SectionStats &operator=(SectionStats const &) = default;\n    SectionStats &operator=(SectionStats &&) = default;\n    virtual ~SectionStats();\n\n    SectionInfo sectionInfo;\n    Counts assertions;\n    double durationInSeconds;\n    bool missingAssertions;\n};\n\nstruct TestCaseStats {\n    TestCaseStats(TestCaseInfo const &_testInfo,\n                  Totals const &_totals,\n                  std::string const &_stdOut,\n                  std::string const &_stdErr,\n                  bool _aborting);\n\n    TestCaseStats(TestCaseStats const &) = default;\n    TestCaseStats(TestCaseStats &&) = default;\n    TestCaseStats &operator=(TestCaseStats const &) = default;\n    TestCaseStats &operator=(TestCaseStats &&) = default;\n    virtual ~TestCaseStats();\n\n    TestCaseInfo testInfo;\n    Totals totals;\n    std::string stdOut;\n    std::string stdErr;\n    bool aborting;\n};\n\nstruct TestGroupStats {\n    TestGroupStats(GroupInfo const &_groupInfo, Totals const &_totals, bool _aborting);\n    TestGroupStats(GroupInfo const &_groupInfo);\n\n    TestGroupStats(TestGroupStats const &) = default;\n    TestGroupStats(TestGroupStats &&) = default;\n    TestGroupStats &operator=(TestGroupStats const &) = default;\n    TestGroupStats &operator=(TestGroupStats &&) = default;\n    virtual ~TestGroupStats();\n\n    GroupInfo groupInfo;\n    Totals totals;\n    bool aborting;\n};\n\nstruct TestRunStats {\n    TestRunStats(TestRunInfo const &_runInfo, Totals const &_totals, bool _aborting);\n\n    TestRunStats(TestRunStats const &) = default;\n    TestRunStats(TestRunStats &&) = default;\n    TestRunStats &operator=(TestRunStats const &) = default;\n    TestRunStats &operator=(TestRunStats &&) = default;\n    virtual ~TestRunStats();\n\n    TestRunInfo runInfo;\n    Totals totals;\n    bool aborting;\n};\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\nstruct BenchmarkInfo {\n    std::string name;\n    double estimatedDuration;\n    int iterations;\n    int samples;\n    unsigned int resamples;\n    double clockResolution;\n    double clockCost;\n};\n\ntemplate <class Duration>\nstruct BenchmarkStats {\n    BenchmarkInfo info;\n\n    std::vector<Duration> samples;\n    Benchmark::Estimate<Duration> mean;\n    Benchmark::Estimate<Duration> standardDeviation;\n    Benchmark::OutlierClassification outliers;\n    double outlierVariance;\n\n    template <typename Duration2>\n    operator BenchmarkStats<Duration2>() const {\n        std::vector<Duration2> samples2;\n        samples2.reserve(samples.size());\n        std::transform(samples.begin(), samples.end(), std::back_inserter(samples2),\n                       [](Duration d) { return Duration2(d); });\n        return {\n                info, std::move(samples2), mean, standardDeviation, outliers, outlierVariance,\n        };\n    }\n};\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\nstruct IStreamingReporter {\n    virtual ~IStreamingReporter() = default;\n\n    // Implementing class must also provide the following static methods:\n    // static std::string getDescription();\n    // static std::set<Verbosity> getSupportedVerbosities()\n\n    virtual ReporterPreferences getPreferences() const = 0;\n\n    virtual void noMatchingTestCases(std::string const &spec) = 0;\n\n    virtual void reportInvalidArguments(std::string const &) {}\n\n    virtual void testRunStarting(TestRunInfo const &testRunInfo) = 0;\n    virtual void testGroupStarting(GroupInfo const &groupInfo) = 0;\n\n    virtual void testCaseStarting(TestCaseInfo const &testInfo) = 0;\n    virtual void sectionStarting(SectionInfo const &sectionInfo) = 0;\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n    virtual void benchmarkPreparing(std::string const &) {}\n    virtual void benchmarkStarting(BenchmarkInfo const &) {}\n    virtual void benchmarkEnded(BenchmarkStats<> const &) {}\n    virtual void benchmarkFailed(std::string const &) {}\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    virtual void assertionStarting(AssertionInfo const &assertionInfo) = 0;\n\n    // The return value indicates if the messages buffer should be cleared:\n    virtual bool assertionEnded(AssertionStats const &assertionStats) = 0;\n\n    virtual void sectionEnded(SectionStats const &sectionStats) = 0;\n    virtual void testCaseEnded(TestCaseStats const &testCaseStats) = 0;\n    virtual void testGroupEnded(TestGroupStats const &testGroupStats) = 0;\n    virtual void testRunEnded(TestRunStats const &testRunStats) = 0;\n\n    virtual void skipTest(TestCaseInfo const &testInfo) = 0;\n\n    // Default empty implementation provided\n    virtual void fatalErrorEncountered(StringRef name);\n\n    virtual bool isMulti() const;\n};\nusing IStreamingReporterPtr = std::unique_ptr<IStreamingReporter>;\n\nstruct IReporterFactory {\n    virtual ~IReporterFactory();\n    virtual IStreamingReporterPtr create(ReporterConfig const &config) const = 0;\n    virtual std::string getDescription() const = 0;\n};\nusing IReporterFactoryPtr = std::shared_ptr<IReporterFactory>;\n\nstruct IReporterRegistry {\n    using FactoryMap = std::map<std::string, IReporterFactoryPtr>;\n    using Listeners = std::vector<IReporterFactoryPtr>;\n\n    virtual ~IReporterRegistry();\n    virtual IStreamingReporterPtr create(std::string const &name,\n                                         IConfigPtr const &config) const = 0;\n    virtual FactoryMap const &getFactories() const = 0;\n    virtual Listeners const &getListeners() const = 0;\n};\n\n}  // end namespace Catch\n\n// end catch_interfaces_reporter.h\n#include <algorithm>\n#include <cassert>\n#include <cfloat>\n#include <cstdio>\n#include <cstring>\n#include <memory>\n#include <ostream>\n\nnamespace Catch {\nvoid prepareExpandedExpression(AssertionResult &result);\n\n// Returns double formatted as %.3f (format expected on output)\nstd::string getFormattedDuration(double duration);\n\n//! Should the reporter show\nbool shouldShowDuration(IConfig const &config, double duration);\n\nstd::string serializeFilters(std::vector<std::string> const &container);\n\ntemplate <typename DerivedT>\nstruct StreamingReporterBase : IStreamingReporter {\n    StreamingReporterBase(ReporterConfig const &_config)\n            : m_config(_config.fullConfig()), stream(_config.stream()) {\n        m_reporterPrefs.shouldRedirectStdOut = false;\n        if (!DerivedT::getSupportedVerbosities().count(m_config->verbosity()))\n            CATCH_ERROR(\"Verbosity level not supported by this reporter\");\n    }\n\n    ReporterPreferences getPreferences() const override { return m_reporterPrefs; }\n\n    static std::set<Verbosity> getSupportedVerbosities() { return {Verbosity::Normal}; }\n\n    ~StreamingReporterBase() override = default;\n\n    void noMatchingTestCases(std::string const &) override {}\n\n    void reportInvalidArguments(std::string const &) override {}\n\n    void testRunStarting(TestRunInfo const &_testRunInfo) override {\n        currentTestRunInfo = _testRunInfo;\n    }\n\n    void testGroupStarting(GroupInfo const &_groupInfo) override { currentGroupInfo = _groupInfo; }\n\n    void testCaseStarting(TestCaseInfo const &_testInfo) override {\n        currentTestCaseInfo = _testInfo;\n    }\n    void sectionStarting(SectionInfo const &_sectionInfo) override {\n        m_sectionStack.push_back(_sectionInfo);\n    }\n\n    void sectionEnded(SectionStats const & /* _sectionStats */) override {\n        m_sectionStack.pop_back();\n    }\n    void testCaseEnded(TestCaseStats const & /* _testCaseStats */) override {\n        currentTestCaseInfo.reset();\n    }\n    void testGroupEnded(TestGroupStats const & /* _testGroupStats */) override {\n        currentGroupInfo.reset();\n    }\n    void testRunEnded(TestRunStats const & /* _testRunStats */) override {\n        currentTestCaseInfo.reset();\n        currentGroupInfo.reset();\n        currentTestRunInfo.reset();\n    }\n\n    void skipTest(TestCaseInfo const &) override {\n        // Don't do anything with this by default.\n        // It can optionally be overridden in the derived class.\n    }\n\n    IConfigPtr m_config;\n    std::ostream &stream;\n\n    LazyStat<TestRunInfo> currentTestRunInfo;\n    LazyStat<GroupInfo> currentGroupInfo;\n    LazyStat<TestCaseInfo> currentTestCaseInfo;\n\n    std::vector<SectionInfo> m_sectionStack;\n    ReporterPreferences m_reporterPrefs;\n};\n\ntemplate <typename DerivedT>\nstruct CumulativeReporterBase : IStreamingReporter {\n    template <typename T, typename ChildNodeT>\n    struct Node {\n        explicit Node(T const &_value) : value(_value) {}\n        virtual ~Node() {}\n\n        using ChildNodes = std::vector<std::shared_ptr<ChildNodeT>>;\n        T value;\n        ChildNodes children;\n    };\n    struct SectionNode {\n        explicit SectionNode(SectionStats const &_stats) : stats(_stats) {}\n        virtual ~SectionNode() = default;\n\n        bool operator==(SectionNode const &other) const {\n            return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo;\n        }\n        bool operator==(std::shared_ptr<SectionNode> const &other) const {\n            return operator==(*other);\n        }\n\n        SectionStats stats;\n        using ChildSections = std::vector<std::shared_ptr<SectionNode>>;\n        using Assertions = std::vector<AssertionStats>;\n        ChildSections childSections;\n        Assertions assertions;\n        std::string stdOut;\n        std::string stdErr;\n    };\n\n    struct BySectionInfo {\n        BySectionInfo(SectionInfo const &other) : m_other(other) {}\n        BySectionInfo(BySectionInfo const &other) : m_other(other.m_other) {}\n        bool operator()(std::shared_ptr<SectionNode> const &node) const {\n            return ((node->stats.sectionInfo.name == m_other.name) &&\n                    (node->stats.sectionInfo.lineInfo == m_other.lineInfo));\n        }\n        void operator=(BySectionInfo const &) = delete;\n\n    private:\n        SectionInfo const &m_other;\n    };\n\n    using TestCaseNode = Node<TestCaseStats, SectionNode>;\n    using TestGroupNode = Node<TestGroupStats, TestCaseNode>;\n    using TestRunNode = Node<TestRunStats, TestGroupNode>;\n\n    CumulativeReporterBase(ReporterConfig const &_config)\n            : m_config(_config.fullConfig()), stream(_config.stream()) {\n        m_reporterPrefs.shouldRedirectStdOut = false;\n        if (!DerivedT::getSupportedVerbosities().count(m_config->verbosity()))\n            CATCH_ERROR(\"Verbosity level not supported by this reporter\");\n    }\n    ~CumulativeReporterBase() override = default;\n\n    ReporterPreferences getPreferences() const override { return m_reporterPrefs; }\n\n    static std::set<Verbosity> getSupportedVerbosities() { return {Verbosity::Normal}; }\n\n    void testRunStarting(TestRunInfo const &) override {}\n    void testGroupStarting(GroupInfo const &) override {}\n\n    void testCaseStarting(TestCaseInfo const &) override {}\n\n    void sectionStarting(SectionInfo const &sectionInfo) override {\n        SectionStats incompleteStats(sectionInfo, Counts(), 0, false);\n        std::shared_ptr<SectionNode> node;\n        if (m_sectionStack.empty()) {\n            if (!m_rootSection)\n                m_rootSection = std::make_shared<SectionNode>(incompleteStats);\n            node = m_rootSection;\n        } else {\n            SectionNode &parentNode = *m_sectionStack.back();\n            auto it = std::find_if(parentNode.childSections.begin(), parentNode.childSections.end(),\n                                   BySectionInfo(sectionInfo));\n            if (it == parentNode.childSections.end()) {\n                node = std::make_shared<SectionNode>(incompleteStats);\n                parentNode.childSections.push_back(node);\n            } else\n                node = *it;\n        }\n        m_sectionStack.push_back(node);\n        m_deepestSection = std::move(node);\n    }\n\n    void assertionStarting(AssertionInfo const &) override {}\n\n    bool assertionEnded(AssertionStats const &assertionStats) override {\n        assert(!m_sectionStack.empty());\n        // AssertionResult holds a pointer to a temporary DecomposedExpression,\n        // which getExpandedExpression() calls to build the expression string.\n        // Our section stack copy of the assertionResult will likely outlive the\n        // temporary, so it must be expanded or discarded now to avoid calling\n        // a destroyed object later.\n        prepareExpandedExpression(const_cast<AssertionResult &>(assertionStats.assertionResult));\n        SectionNode &sectionNode = *m_sectionStack.back();\n        sectionNode.assertions.push_back(assertionStats);\n        return true;\n    }\n    void sectionEnded(SectionStats const &sectionStats) override {\n        assert(!m_sectionStack.empty());\n        SectionNode &node = *m_sectionStack.back();\n        node.stats = sectionStats;\n        m_sectionStack.pop_back();\n    }\n    void testCaseEnded(TestCaseStats const &testCaseStats) override {\n        auto node = std::make_shared<TestCaseNode>(testCaseStats);\n        assert(m_sectionStack.size() == 0);\n        node->children.push_back(m_rootSection);\n        m_testCases.push_back(node);\n        m_rootSection.reset();\n\n        assert(m_deepestSection);\n        m_deepestSection->stdOut = testCaseStats.stdOut;\n        m_deepestSection->stdErr = testCaseStats.stdErr;\n    }\n    void testGroupEnded(TestGroupStats const &testGroupStats) override {\n        auto node = std::make_shared<TestGroupNode>(testGroupStats);\n        node->children.swap(m_testCases);\n        m_testGroups.push_back(node);\n    }\n    void testRunEnded(TestRunStats const &testRunStats) override {\n        auto node = std::make_shared<TestRunNode>(testRunStats);\n        node->children.swap(m_testGroups);\n        m_testRuns.push_back(node);\n        testRunEndedCumulative();\n    }\n    virtual void testRunEndedCumulative() = 0;\n\n    void skipTest(TestCaseInfo const &) override {}\n\n    IConfigPtr m_config;\n    std::ostream &stream;\n    std::vector<AssertionStats> m_assertions;\n    std::vector<std::vector<std::shared_ptr<SectionNode>>> m_sections;\n    std::vector<std::shared_ptr<TestCaseNode>> m_testCases;\n    std::vector<std::shared_ptr<TestGroupNode>> m_testGroups;\n\n    std::vector<std::shared_ptr<TestRunNode>> m_testRuns;\n\n    std::shared_ptr<SectionNode> m_rootSection;\n    std::shared_ptr<SectionNode> m_deepestSection;\n    std::vector<std::shared_ptr<SectionNode>> m_sectionStack;\n    ReporterPreferences m_reporterPrefs;\n};\n\ntemplate <char C>\nchar const *getLineOfChars() {\n    static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0};\n    if (!*line) {\n        std::memset(line, C, CATCH_CONFIG_CONSOLE_WIDTH - 1);\n        line[CATCH_CONFIG_CONSOLE_WIDTH - 1] = 0;\n    }\n    return line;\n}\n\nstruct TestEventListenerBase : StreamingReporterBase<TestEventListenerBase> {\n    TestEventListenerBase(ReporterConfig const &_config);\n\n    static std::set<Verbosity> getSupportedVerbosities();\n\n    void assertionStarting(AssertionInfo const &) override;\n    bool assertionEnded(AssertionStats const &) override;\n};\n\n}  // end namespace Catch\n\n// end catch_reporter_bases.hpp\n// start catch_console_colour.h\n\nnamespace Catch {\n\nstruct Colour {\n    enum Code {\n        None = 0,\n\n        White,\n        Red,\n        Green,\n        Blue,\n        Cyan,\n        Yellow,\n        Grey,\n\n        Bright = 0x10,\n\n        BrightRed = Bright | Red,\n        BrightGreen = Bright | Green,\n        LightGrey = Bright | Grey,\n        BrightWhite = Bright | White,\n        BrightYellow = Bright | Yellow,\n\n        // By intention\n        FileName = LightGrey,\n        Warning = BrightYellow,\n        ResultError = BrightRed,\n        ResultSuccess = BrightGreen,\n        ResultExpectedFailure = Warning,\n\n        Error = BrightRed,\n        Success = Green,\n\n        OriginalExpression = Cyan,\n        ReconstructedExpression = BrightYellow,\n\n        SecondaryText = LightGrey,\n        Headers = White\n    };\n\n    // Use constructed object for RAII guard\n    Colour(Code _colourCode);\n    Colour(Colour &&other) noexcept;\n    Colour &operator=(Colour &&other) noexcept;\n    ~Colour();\n\n    // Use static method for one-shot changes\n    static void use(Code _colourCode);\n\nprivate:\n    bool m_moved = false;\n};\n\nstd::ostream &operator<<(std::ostream &os, Colour const &);\n\n}  // end namespace Catch\n\n// end catch_console_colour.h\n// start catch_reporter_registrars.hpp\n\nnamespace Catch {\n\ntemplate <typename T>\nclass ReporterRegistrar {\n    class ReporterFactory : public IReporterFactory {\n        IStreamingReporterPtr create(ReporterConfig const &config) const override {\n            return std::unique_ptr<T>(new T(config));\n        }\n\n        std::string getDescription() const override { return T::getDescription(); }\n    };\n\npublic:\n    explicit ReporterRegistrar(std::string const &name) {\n        getMutableRegistryHub().registerReporter(name, std::make_shared<ReporterFactory>());\n    }\n};\n\ntemplate <typename T>\nclass ListenerRegistrar {\n    class ListenerFactory : public IReporterFactory {\n        IStreamingReporterPtr create(ReporterConfig const &config) const override {\n            return std::unique_ptr<T>(new T(config));\n        }\n        std::string getDescription() const override { return std::string(); }\n    };\n\npublic:\n    ListenerRegistrar() {\n        getMutableRegistryHub().registerListener(std::make_shared<ListenerFactory>());\n    }\n};\n}  // namespace Catch\n\n#if !defined(CATCH_CONFIG_DISABLE)\n\n#define CATCH_REGISTER_REPORTER(name, reporterType)                                         \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                                               \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                                                \\\n    namespace {                                                                             \\\n    Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType(name); \\\n    }                                                                                       \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n#define CATCH_REGISTER_LISTENER(listenerType)                                         \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                                         \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                                          \\\n    namespace {                                                                       \\\n    Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; \\\n    }                                                                                 \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n#else  // CATCH_CONFIG_DISABLE\n\n#define CATCH_REGISTER_REPORTER(name, reporterType)\n#define CATCH_REGISTER_LISTENER(listenerType)\n\n#endif  // CATCH_CONFIG_DISABLE\n\n// end catch_reporter_registrars.hpp\n// Allow users to base their work off existing reporters\n// start catch_reporter_compact.h\n\nnamespace Catch {\n\nstruct CompactReporter : StreamingReporterBase<CompactReporter> {\n    using StreamingReporterBase::StreamingReporterBase;\n\n    ~CompactReporter() override;\n\n    static std::string getDescription();\n\n    void noMatchingTestCases(std::string const &spec) override;\n\n    void assertionStarting(AssertionInfo const &) override;\n\n    bool assertionEnded(AssertionStats const &_assertionStats) override;\n\n    void sectionEnded(SectionStats const &_sectionStats) override;\n\n    void testRunEnded(TestRunStats const &_testRunStats) override;\n};\n\n}  // end namespace Catch\n\n// end catch_reporter_compact.h\n// start catch_reporter_console.h\n\n#if defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable : 4061)  // Not all labels are EXPLICITLY handled in  \\\n                                 // switch Note that 4062 (not all labels are \\\n                                 // handled and default is missing) is enabled\n#endif\n\nnamespace Catch {\n// Fwd decls\nstruct SummaryColumn;\nclass TablePrinter;\n\nstruct ConsoleReporter : StreamingReporterBase<ConsoleReporter> {\n    std::unique_ptr<TablePrinter> m_tablePrinter;\n\n    ConsoleReporter(ReporterConfig const &config);\n    ~ConsoleReporter() override;\n    static std::string getDescription();\n\n    void noMatchingTestCases(std::string const &spec) override;\n\n    void reportInvalidArguments(std::string const &arg) override;\n\n    void assertionStarting(AssertionInfo const &) override;\n\n    bool assertionEnded(AssertionStats const &_assertionStats) override;\n\n    void sectionStarting(SectionInfo const &_sectionInfo) override;\n    void sectionEnded(SectionStats const &_sectionStats) override;\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n    void benchmarkPreparing(std::string const &name) override;\n    void benchmarkStarting(BenchmarkInfo const &info) override;\n    void benchmarkEnded(BenchmarkStats<> const &stats) override;\n    void benchmarkFailed(std::string const &error) override;\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    void testCaseEnded(TestCaseStats const &_testCaseStats) override;\n    void testGroupEnded(TestGroupStats const &_testGroupStats) override;\n    void testRunEnded(TestRunStats const &_testRunStats) override;\n    void testRunStarting(TestRunInfo const &_testRunInfo) override;\n\nprivate:\n    void lazyPrint();\n\n    void lazyPrintWithoutClosingBenchmarkTable();\n    void lazyPrintRunInfo();\n    void lazyPrintGroupInfo();\n    void printTestCaseAndSectionHeader();\n\n    void printClosedHeader(std::string const &_name);\n    void printOpenHeader(std::string const &_name);\n\n    // if string has a : in first line will set indent to follow it on\n    // subsequent lines\n    void printHeaderString(std::string const &_string, std::size_t indent = 0);\n\n    void printTotals(Totals const &totals);\n    void printSummaryRow(std::string const &label,\n                         std::vector<SummaryColumn> const &cols,\n                         std::size_t row);\n\n    void printTotalsDivider(Totals const &totals);\n    void printSummaryDivider();\n    void printTestFilters();\n\nprivate:\n    bool m_headerPrinted = false;\n};\n\n}  // end namespace Catch\n\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n\n// end catch_reporter_console.h\n// start catch_reporter_junit.h\n\n// start catch_xmlwriter.h\n\n#include <vector>\n\nnamespace Catch {\nenum class XmlFormatting {\n    None = 0x00,\n    Indent = 0x01,\n    Newline = 0x02,\n};\n\nXmlFormatting operator|(XmlFormatting lhs, XmlFormatting rhs);\nXmlFormatting operator&(XmlFormatting lhs, XmlFormatting rhs);\n\nclass XmlEncode {\npublic:\n    enum ForWhat { ForTextNodes, ForAttributes };\n\n    XmlEncode(std::string const &str, ForWhat forWhat = ForTextNodes);\n\n    void encodeTo(std::ostream &os) const;\n\n    friend std::ostream &operator<<(std::ostream &os, XmlEncode const &xmlEncode);\n\nprivate:\n    std::string m_str;\n    ForWhat m_forWhat;\n};\n\nclass XmlWriter {\npublic:\n    class ScopedElement {\n    public:\n        ScopedElement(XmlWriter *writer, XmlFormatting fmt);\n\n        ScopedElement(ScopedElement &&other) noexcept;\n        ScopedElement &operator=(ScopedElement &&other) noexcept;\n\n        ~ScopedElement();\n\n        ScopedElement &writeText(std::string const &text,\n                                 XmlFormatting fmt = XmlFormatting::Newline |\n                                                     XmlFormatting::Indent);\n\n        template <typename T>\n        ScopedElement &writeAttribute(std::string const &name, T const &attribute) {\n            m_writer->writeAttribute(name, attribute);\n            return *this;\n        }\n\n    private:\n        mutable XmlWriter *m_writer = nullptr;\n        XmlFormatting m_fmt;\n    };\n\n    XmlWriter(std::ostream &os = Catch::cout());\n    ~XmlWriter();\n\n    XmlWriter(XmlWriter const &) = delete;\n    XmlWriter &operator=(XmlWriter const &) = delete;\n\n    XmlWriter &startElement(std::string const &name,\n                            XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n    ScopedElement scopedElement(std::string const &name,\n                                XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n    XmlWriter &endElement(XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n    XmlWriter &writeAttribute(std::string const &name, std::string const &attribute);\n\n    XmlWriter &writeAttribute(std::string const &name, bool attribute);\n\n    template <typename T>\n    XmlWriter &writeAttribute(std::string const &name, T const &attribute) {\n        ReusableStringStream rss;\n        rss << attribute;\n        return writeAttribute(name, rss.str());\n    }\n\n    XmlWriter &writeText(std::string const &text,\n                         XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n    XmlWriter &writeComment(std::string const &text,\n                            XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n    void writeStylesheetRef(std::string const &url);\n\n    XmlWriter &writeBlankLine();\n\n    void ensureTagClosed();\n\nprivate:\n    void applyFormatting(XmlFormatting fmt);\n\n    void writeDeclaration();\n\n    void newlineIfNecessary();\n\n    bool m_tagIsOpen = false;\n    bool m_needsNewline = false;\n    std::vector<std::string> m_tags;\n    std::string m_indent;\n    std::ostream &m_os;\n};\n\n}  // namespace Catch\n\n// end catch_xmlwriter.h\nnamespace Catch {\n\nclass JunitReporter : public CumulativeReporterBase<JunitReporter> {\npublic:\n    JunitReporter(ReporterConfig const &_config);\n\n    ~JunitReporter() override;\n\n    static std::string getDescription();\n\n    void noMatchingTestCases(std::string const & /*spec*/) override;\n\n    void testRunStarting(TestRunInfo const &runInfo) override;\n\n    void testGroupStarting(GroupInfo const &groupInfo) override;\n\n    void testCaseStarting(TestCaseInfo const &testCaseInfo) override;\n    bool assertionEnded(AssertionStats const &assertionStats) override;\n\n    void testCaseEnded(TestCaseStats const &testCaseStats) override;\n\n    void testGroupEnded(TestGroupStats const &testGroupStats) override;\n\n    void testRunEndedCumulative() override;\n\n    void writeGroup(TestGroupNode const &groupNode, double suiteTime);\n\n    void writeTestCase(TestCaseNode const &testCaseNode);\n\n    void writeSection(std::string const &className,\n                      std::string const &rootName,\n                      SectionNode const &sectionNode,\n                      bool testOkToFail);\n\n    void writeAssertions(SectionNode const &sectionNode);\n    void writeAssertion(AssertionStats const &stats);\n\n    XmlWriter xml;\n    Timer suiteTimer;\n    std::string stdOutForSuite;\n    std::string stdErrForSuite;\n    unsigned int unexpectedExceptions = 0;\n    bool m_okToFail = false;\n};\n\n}  // end namespace Catch\n\n// end catch_reporter_junit.h\n// start catch_reporter_xml.h\n\nnamespace Catch {\nclass XmlReporter : public StreamingReporterBase<XmlReporter> {\npublic:\n    XmlReporter(ReporterConfig const &_config);\n\n    ~XmlReporter() override;\n\n    static std::string getDescription();\n\n    virtual std::string getStylesheetRef() const;\n\n    void writeSourceInfo(SourceLineInfo const &sourceInfo);\n\npublic:  // StreamingReporterBase\n    void noMatchingTestCases(std::string const &s) override;\n\n    void testRunStarting(TestRunInfo const &testInfo) override;\n\n    void testGroupStarting(GroupInfo const &groupInfo) override;\n\n    void testCaseStarting(TestCaseInfo const &testInfo) override;\n\n    void sectionStarting(SectionInfo const &sectionInfo) override;\n\n    void assertionStarting(AssertionInfo const &) override;\n\n    bool assertionEnded(AssertionStats const &assertionStats) override;\n\n    void sectionEnded(SectionStats const &sectionStats) override;\n\n    void testCaseEnded(TestCaseStats const &testCaseStats) override;\n\n    void testGroupEnded(TestGroupStats const &testGroupStats) override;\n\n    void testRunEnded(TestRunStats const &testRunStats) override;\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n    void benchmarkPreparing(std::string const &name) override;\n    void benchmarkStarting(BenchmarkInfo const &) override;\n    void benchmarkEnded(BenchmarkStats<> const &) override;\n    void benchmarkFailed(std::string const &) override;\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\nprivate:\n    Timer m_testCaseTimer;\n    XmlWriter m_xml;\n    int m_sectionDepth = 0;\n};\n\n}  // end namespace Catch\n\n// end catch_reporter_xml.h\n\n// end catch_external_interfaces.h\n#endif\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n// start catch_benchmarking_all.hpp\n\n// A proxy header that includes all of the benchmarking headers to allow\n// concise include of the benchmarking features. You should prefer the\n// individual includes in standard use.\n\n// start catch_benchmark.hpp\n\n// Benchmark\n\n// start catch_chronometer.hpp\n\n// User-facing chronometer\n\n// start catch_clock.hpp\n\n// Clocks\n\n#include <chrono>\n#include <ratio>\n\nnamespace Catch {\nnamespace Benchmark {\ntemplate <typename Clock>\nusing ClockDuration = typename Clock::duration;\ntemplate <typename Clock>\nusing FloatDuration = std::chrono::duration<double, typename Clock::period>;\n\ntemplate <typename Clock>\nusing TimePoint = typename Clock::time_point;\n\nusing default_clock = std::chrono::steady_clock;\n\ntemplate <typename Clock>\nstruct now {\n    TimePoint<Clock> operator()() const { return Clock::now(); }\n};\n\nusing fp_seconds = std::chrono::duration<double, std::ratio<1>>;\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_clock.hpp\n// start catch_optimizer.hpp\n\n// Hinting the optimizer\n\n#if defined(_MSC_VER)\n#include <atomic>  // atomic_thread_fence\n#endif\n\nnamespace Catch {\nnamespace Benchmark {\n#if defined(__GNUC__) || defined(__clang__)\ntemplate <typename T>\ninline void keep_memory(T *p) {\n    asm volatile(\"\" : : \"g\"(p) : \"memory\");\n}\ninline void keep_memory() { asm volatile(\"\" : : : \"memory\"); }\n\nnamespace Detail {\ninline void optimizer_barrier() { keep_memory(); }\n}  // namespace Detail\n#elif defined(_MSC_VER)\n\n#pragma optimize(\"\", off)\ntemplate <typename T>\ninline void keep_memory(T *p) {\n    // thanks @milleniumbug\n    *reinterpret_cast<char volatile *>(p) = *reinterpret_cast<char const volatile *>(p);\n}\n// TODO equivalent keep_memory()\n#pragma optimize(\"\", on)\n\nnamespace Detail {\ninline void optimizer_barrier() { std::atomic_thread_fence(std::memory_order_seq_cst); }\n}  // namespace Detail\n\n#endif\n\ntemplate <typename T>\ninline void deoptimize_value(T &&x) {\n    keep_memory(&x);\n}\n\ntemplate <typename Fn, typename... Args>\ninline auto invoke_deoptimized(Fn &&fn, Args &&...args) ->\n        typename std::enable_if<!std::is_same<void, decltype(fn(args...))>::value>::type {\n    deoptimize_value(std::forward<Fn>(fn)(std::forward<Args...>(args...)));\n}\n\ntemplate <typename Fn, typename... Args>\ninline auto invoke_deoptimized(Fn &&fn, Args &&...args) ->\n        typename std::enable_if<std::is_same<void, decltype(fn(args...))>::value>::type {\n    std::forward<Fn>(fn)(std::forward<Args...>(args...));\n}\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_optimizer.hpp\n// start catch_complete_invoke.hpp\n\n// Invoke with a special case for void\n\n#include <type_traits>\n#include <utility>\n\nnamespace Catch {\nnamespace Benchmark {\nnamespace Detail {\ntemplate <typename T>\nstruct CompleteType {\n    using type = T;\n};\ntemplate <>\nstruct CompleteType<void> {\n    struct type {};\n};\n\ntemplate <typename T>\nusing CompleteType_t = typename CompleteType<T>::type;\n\ntemplate <typename Result>\nstruct CompleteInvoker {\n    template <typename Fun, typename... Args>\n    static Result invoke(Fun &&fun, Args &&...args) {\n        return std::forward<Fun>(fun)(std::forward<Args>(args)...);\n    }\n};\ntemplate <>\nstruct CompleteInvoker<void> {\n    template <typename Fun, typename... Args>\n    static CompleteType_t<void> invoke(Fun &&fun, Args &&...args) {\n        std::forward<Fun>(fun)(std::forward<Args>(args)...);\n        return {};\n    }\n};\n\n// invoke and not return void :(\ntemplate <typename Fun, typename... Args>\nCompleteType_t<FunctionReturnType<Fun, Args...>> complete_invoke(Fun &&fun, Args &&...args) {\n    return CompleteInvoker<FunctionReturnType<Fun, Args...>>::invoke(std::forward<Fun>(fun),\n                                                                     std::forward<Args>(args)...);\n}\n\nconst std::string benchmarkErrorMsg = \"a benchmark failed to run successfully\";\n}  // namespace Detail\n\ntemplate <typename Fun>\nDetail::CompleteType_t<FunctionReturnType<Fun>> user_code(Fun &&fun) {\n    CATCH_TRY { return Detail::complete_invoke(std::forward<Fun>(fun)); }\n    CATCH_CATCH_ALL {\n        getResultCapture().benchmarkFailed(translateActiveException());\n        CATCH_RUNTIME_ERROR(Detail::benchmarkErrorMsg);\n    }\n}\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_complete_invoke.hpp\nnamespace Catch {\nnamespace Benchmark {\nnamespace Detail {\nstruct ChronometerConcept {\n    virtual void start() = 0;\n    virtual void finish() = 0;\n    virtual ~ChronometerConcept() = default;\n};\ntemplate <typename Clock>\nstruct ChronometerModel final : public ChronometerConcept {\n    void start() override { started = Clock::now(); }\n    void finish() override { finished = Clock::now(); }\n\n    ClockDuration<Clock> elapsed() const { return finished - started; }\n\n    TimePoint<Clock> started;\n    TimePoint<Clock> finished;\n};\n}  // namespace Detail\n\nstruct Chronometer {\npublic:\n    template <typename Fun>\n    void measure(Fun &&fun) {\n        measure(std::forward<Fun>(fun), is_callable<Fun(int)>());\n    }\n\n    int runs() const { return k; }\n\n    Chronometer(Detail::ChronometerConcept &meter, int k) : impl(&meter), k(k) {}\n\nprivate:\n    template <typename Fun>\n    void measure(Fun &&fun, std::false_type) {\n        measure([&fun](int) { return fun(); }, std::true_type());\n    }\n\n    template <typename Fun>\n    void measure(Fun &&fun, std::true_type) {\n        Detail::optimizer_barrier();\n        impl->start();\n        for (int i = 0; i < k; ++i)\n            invoke_deoptimized(fun, i);\n        impl->finish();\n        Detail::optimizer_barrier();\n    }\n\n    Detail::ChronometerConcept *impl;\n    int k;\n};\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_chronometer.hpp\n// start catch_environment.hpp\n\n// Environment information\n\nnamespace Catch {\nnamespace Benchmark {\ntemplate <typename Duration>\nstruct EnvironmentEstimate {\n    Duration mean;\n    OutlierClassification outliers;\n\n    template <typename Duration2>\n    operator EnvironmentEstimate<Duration2>() const {\n        return {mean, outliers};\n    }\n};\ntemplate <typename Clock>\nstruct Environment {\n    using clock_type = Clock;\n    EnvironmentEstimate<FloatDuration<Clock>> clock_resolution;\n    EnvironmentEstimate<FloatDuration<Clock>> clock_cost;\n};\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_environment.hpp\n// start catch_execution_plan.hpp\n\n// Execution plan\n\n// start catch_benchmark_function.hpp\n\n// Dumb std::function implementation for consistent call overhead\n\n#include <cassert>\n#include <memory>\n#include <type_traits>\n#include <utility>\n\nnamespace Catch {\nnamespace Benchmark {\nnamespace Detail {\ntemplate <typename T>\nusing Decay = typename std::decay<T>::type;\ntemplate <typename T, typename U>\nstruct is_related : std::is_same<Decay<T>, Decay<U>> {};\n\n/// We need to reinvent std::function because every piece of code that might add\n/// overhead in a measurement context needs to have consistent performance\n/// characteristics so that we can account for it in the measurement.\n/// Implementations of std::function with optimizations that aren't always\n/// applicable, like small buffer optimizations, are not uncommon. This is\n/// effectively an implementation of std::function without any such\n/// optimizations; it may be slow, but it is consistently slow.\nstruct BenchmarkFunction {\nprivate:\n    struct callable {\n        virtual void call(Chronometer meter) const = 0;\n        virtual callable *clone() const = 0;\n        virtual ~callable() = default;\n    };\n    template <typename Fun>\n    struct model : public callable {\n        model(Fun &&fun) : fun(std::move(fun)) {}\n        model(Fun const &fun) : fun(fun) {}\n\n        model<Fun> *clone() const override { return new model<Fun>(*this); }\n\n        void call(Chronometer meter) const override {\n            call(meter, is_callable<Fun(Chronometer)>());\n        }\n        void call(Chronometer meter, std::true_type) const { fun(meter); }\n        void call(Chronometer meter, std::false_type) const { meter.measure(fun); }\n\n        Fun fun;\n    };\n\n    struct do_nothing {\n        void operator()() const {}\n    };\n\n    template <typename T>\n    BenchmarkFunction(model<T> *c) : f(c) {}\n\npublic:\n    BenchmarkFunction() : f(new model<do_nothing>{{}}) {}\n\n    template <typename Fun,\n              typename std::enable_if<!is_related<Fun, BenchmarkFunction>::value, int>::type = 0>\n    BenchmarkFunction(Fun &&fun)\n            : f(new model<typename std::decay<Fun>::type>(std::forward<Fun>(fun))) {}\n\n    BenchmarkFunction(BenchmarkFunction &&that) : f(std::move(that.f)) {}\n\n    BenchmarkFunction(BenchmarkFunction const &that) : f(that.f->clone()) {}\n\n    BenchmarkFunction &operator=(BenchmarkFunction &&that) {\n        f = std::move(that.f);\n        return *this;\n    }\n\n    BenchmarkFunction &operator=(BenchmarkFunction const &that) {\n        f.reset(that.f->clone());\n        return *this;\n    }\n\n    void operator()(Chronometer meter) const { f->call(meter); }\n\nprivate:\n    std::unique_ptr<callable> f;\n};\n}  // namespace Detail\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_benchmark_function.hpp\n// start catch_repeat.hpp\n\n// repeat algorithm\n\n#include <type_traits>\n#include <utility>\n\nnamespace Catch {\nnamespace Benchmark {\nnamespace Detail {\ntemplate <typename Fun>\nstruct repeater {\n    void operator()(int k) const {\n        for (int i = 0; i < k; ++i) {\n            fun();\n        }\n    }\n    Fun fun;\n};\ntemplate <typename Fun>\nrepeater<typename std::decay<Fun>::type> repeat(Fun &&fun) {\n    return {std::forward<Fun>(fun)};\n}\n}  // namespace Detail\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_repeat.hpp\n// start catch_run_for_at_least.hpp\n\n// Run a function for a minimum amount of time\n\n// start catch_measure.hpp\n\n// Measure\n\n// start catch_timing.hpp\n\n// Timing\n\n#include <tuple>\n#include <type_traits>\n\nnamespace Catch {\nnamespace Benchmark {\ntemplate <typename Duration, typename Result>\nstruct Timing {\n    Duration elapsed;\n    Result result;\n    int iterations;\n};\ntemplate <typename Clock, typename Func, typename... Args>\nusing TimingOf =\n        Timing<ClockDuration<Clock>, Detail::CompleteType_t<FunctionReturnType<Func, Args...>>>;\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_timing.hpp\n#include <utility>\n\nnamespace Catch {\nnamespace Benchmark {\nnamespace Detail {\ntemplate <typename Clock, typename Fun, typename... Args>\nTimingOf<Clock, Fun, Args...> measure(Fun &&fun, Args &&...args) {\n    auto start = Clock::now();\n    auto &&r = Detail::complete_invoke(fun, std::forward<Args>(args)...);\n    auto end = Clock::now();\n    auto delta = end - start;\n    return {delta, std::forward<decltype(r)>(r), 1};\n}\n}  // namespace Detail\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_measure.hpp\n#include <type_traits>\n#include <utility>\n\nnamespace Catch {\nnamespace Benchmark {\nnamespace Detail {\ntemplate <typename Clock, typename Fun>\nTimingOf<Clock, Fun, int> measure_one(Fun &&fun, int iters, std::false_type) {\n    return Detail::measure<Clock>(fun, iters);\n}\ntemplate <typename Clock, typename Fun>\nTimingOf<Clock, Fun, Chronometer> measure_one(Fun &&fun, int iters, std::true_type) {\n    Detail::ChronometerModel<Clock> meter;\n    auto &&result = Detail::complete_invoke(fun, Chronometer(meter, iters));\n\n    return {meter.elapsed(), std::move(result), iters};\n}\n\ntemplate <typename Clock, typename Fun>\nusing run_for_at_least_argument_t =\n        typename std::conditional<is_callable<Fun(Chronometer)>::value, Chronometer, int>::type;\n\nstruct optimized_away_error : std::exception {\n    const char *what() const noexcept override {\n        return \"could not measure benchmark, maybe it was optimized away\";\n    }\n};\n\ntemplate <typename Clock, typename Fun>\nTimingOf<Clock, Fun, run_for_at_least_argument_t<Clock, Fun>>\nrun_for_at_least(ClockDuration<Clock> how_long, int seed, Fun &&fun) {\n    auto iters = seed;\n    while (iters < (1 << 30)) {\n        auto &&Timing = measure_one<Clock>(fun, iters, is_callable<Fun(Chronometer)>());\n\n        if (Timing.elapsed >= how_long) {\n            return {Timing.elapsed, std::move(Timing.result), iters};\n        }\n        iters *= 2;\n    }\n    Catch::throw_exception(optimized_away_error{});\n}\n}  // namespace Detail\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_run_for_at_least.hpp\n#include <algorithm>\n#include <iterator>\n\nnamespace Catch {\nnamespace Benchmark {\ntemplate <typename Duration>\nstruct ExecutionPlan {\n    int iterations_per_sample;\n    Duration estimated_duration;\n    Detail::BenchmarkFunction benchmark;\n    Duration warmup_time;\n    int warmup_iterations;\n\n    template <typename Duration2>\n    operator ExecutionPlan<Duration2>() const {\n        return {iterations_per_sample, estimated_duration, benchmark, warmup_time,\n                warmup_iterations};\n    }\n\n    template <typename Clock>\n    std::vector<FloatDuration<Clock>> run(const IConfig &cfg,\n                                          Environment<FloatDuration<Clock>> env) const {\n        // warmup a bit\n        Detail::run_for_at_least<Clock>(\n                std::chrono::duration_cast<ClockDuration<Clock>>(warmup_time), warmup_iterations,\n                Detail::repeat(now<Clock>{}));\n\n        std::vector<FloatDuration<Clock>> times;\n        times.reserve(cfg.benchmarkSamples());\n        std::generate_n(std::back_inserter(times), cfg.benchmarkSamples(), [this, env] {\n            Detail::ChronometerModel<Clock> model;\n            this->benchmark(Chronometer(model, iterations_per_sample));\n            auto sample_time = model.elapsed() - env.clock_cost.mean;\n            if (sample_time < FloatDuration<Clock>::zero())\n                sample_time = FloatDuration<Clock>::zero();\n            return sample_time / iterations_per_sample;\n        });\n        return times;\n    }\n};\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_execution_plan.hpp\n// start catch_estimate_clock.hpp\n\n// Environment measurement\n\n// start catch_stats.hpp\n\n// Statistical analysis tools\n\n#include <algorithm>\n#include <cmath>\n#include <cstddef>\n#include <functional>\n#include <iterator>\n#include <numeric>\n#include <random>\n#include <tuple>\n#include <utility>\n#include <vector>\n\nnamespace Catch {\nnamespace Benchmark {\nnamespace Detail {\nusing sample = std::vector<double>;\n\ndouble weighted_average_quantile(int k,\n                                 int q,\n                                 std::vector<double>::iterator first,\n                                 std::vector<double>::iterator last);\n\ntemplate <typename Iterator>\nOutlierClassification classify_outliers(Iterator first, Iterator last) {\n    std::vector<double> copy(first, last);\n\n    auto q1 = weighted_average_quantile(1, 4, copy.begin(), copy.end());\n    auto q3 = weighted_average_quantile(3, 4, copy.begin(), copy.end());\n    auto iqr = q3 - q1;\n    auto los = q1 - (iqr * 3.);\n    auto lom = q1 - (iqr * 1.5);\n    auto him = q3 + (iqr * 1.5);\n    auto his = q3 + (iqr * 3.);\n\n    OutlierClassification o;\n    for (; first != last; ++first) {\n        auto &&t = *first;\n        if (t < los)\n            ++o.low_severe;\n        else if (t < lom)\n            ++o.low_mild;\n        else if (t > his)\n            ++o.high_severe;\n        else if (t > him)\n            ++o.high_mild;\n        ++o.samples_seen;\n    }\n    return o;\n}\n\ntemplate <typename Iterator>\ndouble mean(Iterator first, Iterator last) {\n    auto count = last - first;\n    double sum = std::accumulate(first, last, 0.);\n    return sum / count;\n}\n\ntemplate <typename URng, typename Iterator, typename Estimator>\nsample resample(URng &rng, int resamples, Iterator first, Iterator last, Estimator &estimator) {\n    auto n = last - first;\n    std::uniform_int_distribution<decltype(n)> dist(0, n - 1);\n\n    sample out;\n    out.reserve(resamples);\n    std::generate_n(std::back_inserter(out), resamples, [n, first, &estimator, &dist, &rng] {\n        std::vector<double> resampled;\n        resampled.reserve(n);\n        std::generate_n(std::back_inserter(resampled), n,\n                        [first, &dist, &rng] { return first[dist(rng)]; });\n        return estimator(resampled.begin(), resampled.end());\n    });\n    std::sort(out.begin(), out.end());\n    return out;\n}\n\ntemplate <typename Estimator, typename Iterator>\nsample jackknife(Estimator &&estimator, Iterator first, Iterator last) {\n    auto n = last - first;\n    auto second = std::next(first);\n    sample results;\n    results.reserve(n);\n\n    for (auto it = first; it != last; ++it) {\n        std::iter_swap(it, first);\n        results.push_back(estimator(second, last));\n    }\n\n    return results;\n}\n\ninline double normal_cdf(double x) { return std::erfc(-x / std::sqrt(2.0)) / 2.0; }\n\ndouble erfc_inv(double x);\n\ndouble normal_quantile(double p);\n\ntemplate <typename Iterator, typename Estimator>\nEstimate<double> bootstrap(double confidence_level,\n                           Iterator first,\n                           Iterator last,\n                           sample const &resample,\n                           Estimator &&estimator) {\n    auto n_samples = last - first;\n\n    double point = estimator(first, last);\n    // Degenerate case with a single sample\n    if (n_samples == 1)\n        return {point, point, point, confidence_level};\n\n    sample jack = jackknife(estimator, first, last);\n    double jack_mean = mean(jack.begin(), jack.end());\n    double sum_squares, sum_cubes;\n    std::tie(sum_squares, sum_cubes) = std::accumulate(\n            jack.begin(), jack.end(), std::make_pair(0., 0.),\n            [jack_mean](std::pair<double, double> sqcb, double x) -> std::pair<double, double> {\n                auto d = jack_mean - x;\n                auto d2 = d * d;\n                auto d3 = d2 * d;\n                return {sqcb.first + d2, sqcb.second + d3};\n            });\n\n    double accel = sum_cubes / (6 * std::pow(sum_squares, 1.5));\n    int n = static_cast<int>(resample.size());\n    double prob_n = std::count_if(resample.begin(), resample.end(),\n                                  [point](double x) { return x < point; }) /\n                    (double)n;\n    // degenerate case with uniform samples\n    if (prob_n == 0)\n        return {point, point, point, confidence_level};\n\n    double bias = normal_quantile(prob_n);\n    double z1 = normal_quantile((1. - confidence_level) / 2.);\n\n    auto cumn = [n](double x) -> int { return std::lround(normal_cdf(x) * n); };\n    auto a = [bias, accel](double b) { return bias + b / (1. - accel * b); };\n    double b1 = bias + z1;\n    double b2 = bias - z1;\n    double a1 = a(b1);\n    double a2 = a(b2);\n    auto lo = (std::max)(cumn(a1), 0);\n    auto hi = (std::min)(cumn(a2), n - 1);\n\n    return {point, resample[lo], resample[hi], confidence_level};\n}\n\ndouble outlier_variance(Estimate<double> mean, Estimate<double> stddev, int n);\n\nstruct bootstrap_analysis {\n    Estimate<double> mean;\n    Estimate<double> standard_deviation;\n    double outlier_variance;\n};\n\nbootstrap_analysis analyse_samples(double confidence_level,\n                                   int n_resamples,\n                                   std::vector<double>::iterator first,\n                                   std::vector<double>::iterator last);\n}  // namespace Detail\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_stats.hpp\n#include <algorithm>\n#include <cmath>\n#include <iterator>\n#include <tuple>\n#include <vector>\n\nnamespace Catch {\nnamespace Benchmark {\nnamespace Detail {\ntemplate <typename Clock>\nstd::vector<double> resolution(int k) {\n    std::vector<TimePoint<Clock>> times;\n    times.reserve(k + 1);\n    std::generate_n(std::back_inserter(times), k + 1, now<Clock>{});\n\n    std::vector<double> deltas;\n    deltas.reserve(k);\n    std::transform(std::next(times.begin()), times.end(), times.begin(), std::back_inserter(deltas),\n                   [](TimePoint<Clock> a, TimePoint<Clock> b) {\n                       return static_cast<double>((a - b).count());\n                   });\n\n    return deltas;\n}\n\nconst auto warmup_iterations = 10000;\nconst auto warmup_time = std::chrono::milliseconds(100);\nconst auto minimum_ticks = 1000;\nconst auto warmup_seed = 10000;\nconst auto clock_resolution_estimation_time = std::chrono::milliseconds(500);\nconst auto clock_cost_estimation_time_limit = std::chrono::seconds(1);\nconst auto clock_cost_estimation_tick_limit = 100000;\nconst auto clock_cost_estimation_time = std::chrono::milliseconds(10);\nconst auto clock_cost_estimation_iterations = 10000;\n\ntemplate <typename Clock>\nint warmup() {\n    return run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(warmup_time),\n                                   warmup_seed, &resolution<Clock>)\n            .iterations;\n}\ntemplate <typename Clock>\nEnvironmentEstimate<FloatDuration<Clock>> estimate_clock_resolution(int iterations) {\n    auto r = run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(\n                                             clock_resolution_estimation_time),\n                                     iterations, &resolution<Clock>)\n                     .result;\n    return {\n            FloatDuration<Clock>(mean(r.begin(), r.end())),\n            classify_outliers(r.begin(), r.end()),\n    };\n}\ntemplate <typename Clock>\nEnvironmentEstimate<FloatDuration<Clock>> estimate_clock_cost(FloatDuration<Clock> resolution) {\n    auto time_limit = (std::min)(resolution * clock_cost_estimation_tick_limit,\n                                 FloatDuration<Clock>(clock_cost_estimation_time_limit));\n    auto time_clock = [](int k) {\n        return Detail::measure<Clock>([k] {\n                   for (int i = 0; i < k; ++i) {\n                       volatile auto ignored = Clock::now();\n                       (void)ignored;\n                   }\n               })\n                .elapsed;\n    };\n    time_clock(1);\n    int iters = clock_cost_estimation_iterations;\n    auto &&r = run_for_at_least<Clock>(\n            std::chrono::duration_cast<ClockDuration<Clock>>(clock_cost_estimation_time), iters,\n            time_clock);\n    std::vector<double> times;\n    int nsamples = static_cast<int>(std::ceil(time_limit / r.elapsed));\n    times.reserve(nsamples);\n    std::generate_n(std::back_inserter(times), nsamples, [time_clock, &r] {\n        return static_cast<double>((time_clock(r.iterations) / r.iterations).count());\n    });\n    return {\n            FloatDuration<Clock>(mean(times.begin(), times.end())),\n            classify_outliers(times.begin(), times.end()),\n    };\n}\n\ntemplate <typename Clock>\nEnvironment<FloatDuration<Clock>> measure_environment() {\n    static Environment<FloatDuration<Clock>> *env = nullptr;\n    if (env) {\n        return *env;\n    }\n\n    auto iters = Detail::warmup<Clock>();\n    auto resolution = Detail::estimate_clock_resolution<Clock>(iters);\n    auto cost = Detail::estimate_clock_cost<Clock>(resolution.mean);\n\n    env = new Environment<FloatDuration<Clock>>{resolution, cost};\n    return *env;\n}\n}  // namespace Detail\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_estimate_clock.hpp\n// start catch_analyse.hpp\n\n// Run and analyse one benchmark\n\n// start catch_sample_analysis.hpp\n\n// Benchmark results\n\n#include <algorithm>\n#include <iterator>\n#include <string>\n#include <vector>\n\nnamespace Catch {\nnamespace Benchmark {\ntemplate <typename Duration>\nstruct SampleAnalysis {\n    std::vector<Duration> samples;\n    Estimate<Duration> mean;\n    Estimate<Duration> standard_deviation;\n    OutlierClassification outliers;\n    double outlier_variance;\n\n    template <typename Duration2>\n    operator SampleAnalysis<Duration2>() const {\n        std::vector<Duration2> samples2;\n        samples2.reserve(samples.size());\n        std::transform(samples.begin(), samples.end(), std::back_inserter(samples2),\n                       [](Duration d) { return Duration2(d); });\n        return {\n                std::move(samples2), mean, standard_deviation, outliers, outlier_variance,\n        };\n    }\n};\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_sample_analysis.hpp\n#include <algorithm>\n#include <iterator>\n#include <vector>\n\nnamespace Catch {\nnamespace Benchmark {\nnamespace Detail {\ntemplate <typename Duration, typename Iterator>\nSampleAnalysis<Duration> analyse(const IConfig &cfg,\n                                 Environment<Duration>,\n                                 Iterator first,\n                                 Iterator last) {\n    if (!cfg.benchmarkNoAnalysis()) {\n        std::vector<double> samples;\n        samples.reserve(last - first);\n        std::transform(first, last, std::back_inserter(samples),\n                       [](Duration d) { return d.count(); });\n\n        auto analysis = Catch::Benchmark::Detail::analyse_samples(cfg.benchmarkConfidenceInterval(),\n                                                                  cfg.benchmarkResamples(),\n                                                                  samples.begin(), samples.end());\n        auto outliers = Catch::Benchmark::Detail::classify_outliers(samples.begin(), samples.end());\n\n        auto wrap_estimate = [](Estimate<double> e) {\n            return Estimate<Duration>{\n                    Duration(e.point),\n                    Duration(e.lower_bound),\n                    Duration(e.upper_bound),\n                    e.confidence_interval,\n            };\n        };\n        std::vector<Duration> samples2;\n        samples2.reserve(samples.size());\n        std::transform(samples.begin(), samples.end(), std::back_inserter(samples2),\n                       [](double d) { return Duration(d); });\n        return {\n                std::move(samples2),\n                wrap_estimate(analysis.mean),\n                wrap_estimate(analysis.standard_deviation),\n                outliers,\n                analysis.outlier_variance,\n        };\n    } else {\n        std::vector<Duration> samples;\n        samples.reserve(last - first);\n\n        Duration mean = Duration(0);\n        int i = 0;\n        for (auto it = first; it < last; ++it, ++i) {\n            samples.push_back(Duration(*it));\n            mean += Duration(*it);\n        }\n        mean /= i;\n\n        return {std::move(samples), Estimate<Duration>{mean, mean, mean, 0.0},\n                Estimate<Duration>{Duration(0), Duration(0), Duration(0), 0.0},\n                OutlierClassification{}, 0.0};\n    }\n}\n}  // namespace Detail\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_analyse.hpp\n#include <algorithm>\n#include <cmath>\n#include <functional>\n#include <string>\n#include <vector>\n\nnamespace Catch {\nnamespace Benchmark {\nstruct Benchmark {\n    Benchmark(std::string &&name) : name(std::move(name)) {}\n\n    template <class FUN>\n    Benchmark(std::string &&name, FUN &&func) : fun(std::move(func)), name(std::move(name)) {}\n\n    template <typename Clock>\n    ExecutionPlan<FloatDuration<Clock>> prepare(const IConfig &cfg,\n                                                Environment<FloatDuration<Clock>> env) const {\n        auto min_time = env.clock_resolution.mean * Detail::minimum_ticks;\n        auto run_time =\n                std::max(min_time,\n                         std::chrono::duration_cast<decltype(min_time)>(cfg.benchmarkWarmupTime()));\n        auto &&test = Detail::run_for_at_least<Clock>(\n                std::chrono::duration_cast<ClockDuration<Clock>>(run_time), 1, fun);\n        int new_iters = static_cast<int>(std::ceil(min_time * test.iterations / test.elapsed));\n        return {new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), fun,\n                std::chrono::duration_cast<FloatDuration<Clock>>(cfg.benchmarkWarmupTime()),\n                Detail::warmup_iterations};\n    }\n\n    template <typename Clock = default_clock>\n    void run() {\n        IConfigPtr cfg = getCurrentContext().getConfig();\n\n        auto env = Detail::measure_environment<Clock>();\n\n        getResultCapture().benchmarkPreparing(name);\n        CATCH_TRY {\n            auto plan = user_code([&] { return prepare<Clock>(*cfg, env); });\n\n            BenchmarkInfo info{name,\n                               plan.estimated_duration.count(),\n                               plan.iterations_per_sample,\n                               cfg->benchmarkSamples(),\n                               cfg->benchmarkResamples(),\n                               env.clock_resolution.mean.count(),\n                               env.clock_cost.mean.count()};\n\n            getResultCapture().benchmarkStarting(info);\n\n            auto samples = user_code([&] { return plan.template run<Clock>(*cfg, env); });\n\n            auto analysis = Detail::analyse(*cfg, env, samples.begin(), samples.end());\n            BenchmarkStats<FloatDuration<Clock>> stats{info,\n                                                       analysis.samples,\n                                                       analysis.mean,\n                                                       analysis.standard_deviation,\n                                                       analysis.outliers,\n                                                       analysis.outlier_variance};\n            getResultCapture().benchmarkEnded(stats);\n        }\n        CATCH_CATCH_ALL {\n            if (translateActiveException() !=\n                Detail::benchmarkErrorMsg)  // benchmark errors have been reported,\n                                            // otherwise rethrow.\n                std::rethrow_exception(std::current_exception());\n        }\n    }\n\n    // sets lambda to be used in fun *and* executes benchmark!\n    template <typename Fun,\n              typename std::enable_if<!Detail::is_related<Fun, Benchmark>::value, int>::type = 0>\n    Benchmark &operator=(Fun func) {\n        fun = Detail::BenchmarkFunction(func);\n        run();\n        return *this;\n    }\n\n    explicit operator bool() { return true; }\n\nprivate:\n    Detail::BenchmarkFunction fun;\n    std::string name;\n};\n}  // namespace Benchmark\n}  // namespace Catch\n\n#define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1\n#define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2\n\n#define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex) \\\n    if (Catch::Benchmark::Benchmark BenchmarkName{name})              \\\n    BenchmarkName = [&](int benchmarkIndex)\n\n#define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name) \\\n    if (Catch::Benchmark::Benchmark BenchmarkName{name})       \\\n    BenchmarkName = [&]\n\n// end catch_benchmark.hpp\n// start catch_constructor.hpp\n\n// Constructor and destructor helpers\n\n#include <type_traits>\n\nnamespace Catch {\nnamespace Benchmark {\nnamespace Detail {\ntemplate <typename T, bool Destruct>\nstruct ObjectStorage {\n    using TStorage = typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type;\n\n    ObjectStorage() : data() {}\n\n    ObjectStorage(const ObjectStorage &other) { new (&data) T(other.stored_object()); }\n\n    ObjectStorage(ObjectStorage &&other) { new (&data) T(std::move(other.stored_object())); }\n\n    ~ObjectStorage() { destruct_on_exit<T>(); }\n\n    template <typename... Args>\n    void construct(Args &&...args) {\n        new (&data) T(std::forward<Args>(args)...);\n    }\n\n    template <bool AllowManualDestruction = !Destruct>\n    typename std::enable_if<AllowManualDestruction>::type destruct() {\n        stored_object().~T();\n    }\n\nprivate:\n    // If this is a constructor benchmark, destruct the underlying object\n    template <typename U>\n    void destruct_on_exit(typename std::enable_if<Destruct, U>::type * = 0) {\n        destruct<true>();\n    }\n    // Otherwise, don't\n    template <typename U>\n    void destruct_on_exit(typename std::enable_if<!Destruct, U>::type * = 0) {}\n\n    T &stored_object() { return *static_cast<T *>(static_cast<void *>(&data)); }\n\n    T const &stored_object() const { return *static_cast<T *>(static_cast<void *>(&data)); }\n\n    TStorage data;\n};\n}  // namespace Detail\n\ntemplate <typename T>\nusing storage_for = Detail::ObjectStorage<T, true>;\n\ntemplate <typename T>\nusing destructable_object = Detail::ObjectStorage<T, false>;\n}  // namespace Benchmark\n}  // namespace Catch\n\n// end catch_constructor.hpp\n// end catch_benchmarking_all.hpp\n#endif\n\n#endif  // ! CATCH_CONFIG_IMPL_ONLY\n\n#ifdef CATCH_IMPL\n// start catch_impl.hpp\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wweak-vtables\"\n#endif\n\n// Keep these here for external reporters\n// start catch_test_case_tracker.h\n\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace Catch {\nnamespace TestCaseTracking {\n\nstruct NameAndLocation {\n    std::string name;\n    SourceLineInfo location;\n\n    NameAndLocation(std::string const &_name, SourceLineInfo const &_location);\n    friend bool operator==(NameAndLocation const &lhs, NameAndLocation const &rhs) {\n        return lhs.name == rhs.name && lhs.location == rhs.location;\n    }\n};\n\nclass ITracker;\n\nusing ITrackerPtr = std::shared_ptr<ITracker>;\n\nclass ITracker {\n    NameAndLocation m_nameAndLocation;\n\npublic:\n    ITracker(NameAndLocation const &nameAndLoc) : m_nameAndLocation(nameAndLoc) {}\n\n    // static queries\n    NameAndLocation const &nameAndLocation() const { return m_nameAndLocation; }\n\n    virtual ~ITracker();\n\n    // dynamic queries\n    virtual bool isComplete() const = 0;  // Successfully completed or failed\n    virtual bool isSuccessfullyCompleted() const = 0;\n    virtual bool isOpen() const = 0;  // Started but not complete\n    virtual bool hasChildren() const = 0;\n    virtual bool hasStarted() const = 0;\n\n    virtual ITracker &parent() = 0;\n\n    // actions\n    virtual void close() = 0;  // Successfully complete\n    virtual void fail() = 0;\n    virtual void markAsNeedingAnotherRun() = 0;\n\n    virtual void addChild(ITrackerPtr const &child) = 0;\n    virtual ITrackerPtr findChild(NameAndLocation const &nameAndLocation) = 0;\n    virtual void openChild() = 0;\n\n    // Debug/ checking\n    virtual bool isSectionTracker() const = 0;\n    virtual bool isGeneratorTracker() const = 0;\n};\n\nclass TrackerContext {\n    enum RunState { NotStarted, Executing, CompletedCycle };\n\n    ITrackerPtr m_rootTracker;\n    ITracker *m_currentTracker = nullptr;\n    RunState m_runState = NotStarted;\n\npublic:\n    ITracker &startRun();\n    void endRun();\n\n    void startCycle();\n    void completeCycle();\n\n    bool completedCycle() const;\n    ITracker &currentTracker();\n    void setCurrentTracker(ITracker *tracker);\n};\n\nclass TrackerBase : public ITracker {\nprotected:\n    enum CycleState {\n        NotStarted,\n        Executing,\n        ExecutingChildren,\n        NeedsAnotherRun,\n        CompletedSuccessfully,\n        Failed\n    };\n\n    using Children = std::vector<ITrackerPtr>;\n    TrackerContext &m_ctx;\n    ITracker *m_parent;\n    Children m_children;\n    CycleState m_runState = NotStarted;\n\npublic:\n    TrackerBase(NameAndLocation const &nameAndLocation, TrackerContext &ctx, ITracker *parent);\n\n    bool isComplete() const override;\n    bool isSuccessfullyCompleted() const override;\n    bool isOpen() const override;\n    bool hasChildren() const override;\n    bool hasStarted() const override { return m_runState != NotStarted; }\n\n    void addChild(ITrackerPtr const &child) override;\n\n    ITrackerPtr findChild(NameAndLocation const &nameAndLocation) override;\n    ITracker &parent() override;\n\n    void openChild() override;\n\n    bool isSectionTracker() const override;\n    bool isGeneratorTracker() const override;\n\n    void open();\n\n    void close() override;\n    void fail() override;\n    void markAsNeedingAnotherRun() override;\n\nprivate:\n    void moveToParent();\n    void moveToThis();\n};\n\nclass SectionTracker : public TrackerBase {\n    std::vector<std::string> m_filters;\n    std::string m_trimmed_name;\n\npublic:\n    SectionTracker(NameAndLocation const &nameAndLocation, TrackerContext &ctx, ITracker *parent);\n\n    bool isSectionTracker() const override;\n\n    bool isComplete() const override;\n\n    static SectionTracker &acquire(TrackerContext &ctx, NameAndLocation const &nameAndLocation);\n\n    void tryOpen();\n\n    void addInitialFilters(std::vector<std::string> const &filters);\n    void addNextFilters(std::vector<std::string> const &filters);\n    //! Returns filters active in this tracker\n    std::vector<std::string> const &getFilters() const;\n    //! Returns whitespace-trimmed name of the tracked section\n    std::string const &trimmedName() const;\n};\n\n}  // namespace TestCaseTracking\n\nusing TestCaseTracking::ITracker;\nusing TestCaseTracking::SectionTracker;\nusing TestCaseTracking::TrackerContext;\n\n}  // namespace Catch\n\n// end catch_test_case_tracker.h\n\n// start catch_leak_detector.h\n\nnamespace Catch {\n\nstruct LeakDetector {\n    LeakDetector();\n    ~LeakDetector();\n};\n\n}  // namespace Catch\n// end catch_leak_detector.h\n// Cpp files will be included in the single-header file here\n// start catch_stats.cpp\n\n// Statistical analysis tools\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n\n#include <cassert>\n#include <random>\n\n#if defined(CATCH_CONFIG_USE_ASYNC)\n#include <future>\n#endif\n\nnamespace {\ndouble erf_inv(double x) {\n    // Code accompanying the article \"Approximating the erfinv function\" in GPU\n    // Computing Gems, Volume 2\n    double w, p;\n\n    w = -log((1.0 - x) * (1.0 + x));\n\n    if (w < 6.250000) {\n        w = w - 3.125000;\n        p = -3.6444120640178196996e-21;\n        p = -1.685059138182016589e-19 + p * w;\n        p = 1.2858480715256400167e-18 + p * w;\n        p = 1.115787767802518096e-17 + p * w;\n        p = -1.333171662854620906e-16 + p * w;\n        p = 2.0972767875968561637e-17 + p * w;\n        p = 6.6376381343583238325e-15 + p * w;\n        p = -4.0545662729752068639e-14 + p * w;\n        p = -8.1519341976054721522e-14 + p * w;\n        p = 2.6335093153082322977e-12 + p * w;\n        p = -1.2975133253453532498e-11 + p * w;\n        p = -5.4154120542946279317e-11 + p * w;\n        p = 1.051212273321532285e-09 + p * w;\n        p = -4.1126339803469836976e-09 + p * w;\n        p = -2.9070369957882005086e-08 + p * w;\n        p = 4.2347877827932403518e-07 + p * w;\n        p = -1.3654692000834678645e-06 + p * w;\n        p = -1.3882523362786468719e-05 + p * w;\n        p = 0.0001867342080340571352 + p * w;\n        p = -0.00074070253416626697512 + p * w;\n        p = -0.0060336708714301490533 + p * w;\n        p = 0.24015818242558961693 + p * w;\n        p = 1.6536545626831027356 + p * w;\n    } else if (w < 16.000000) {\n        w = sqrt(w) - 3.250000;\n        p = 2.2137376921775787049e-09;\n        p = 9.0756561938885390979e-08 + p * w;\n        p = -2.7517406297064545428e-07 + p * w;\n        p = 1.8239629214389227755e-08 + p * w;\n        p = 1.5027403968909827627e-06 + p * w;\n        p = -4.013867526981545969e-06 + p * w;\n        p = 2.9234449089955446044e-06 + p * w;\n        p = 1.2475304481671778723e-05 + p * w;\n        p = -4.7318229009055733981e-05 + p * w;\n        p = 6.8284851459573175448e-05 + p * w;\n        p = 2.4031110387097893999e-05 + p * w;\n        p = -0.0003550375203628474796 + p * w;\n        p = 0.00095328937973738049703 + p * w;\n        p = -0.0016882755560235047313 + p * w;\n        p = 0.0024914420961078508066 + p * w;\n        p = -0.0037512085075692412107 + p * w;\n        p = 0.005370914553590063617 + p * w;\n        p = 1.0052589676941592334 + p * w;\n        p = 3.0838856104922207635 + p * w;\n    } else {\n        w = sqrt(w) - 5.000000;\n        p = -2.7109920616438573243e-11;\n        p = -2.5556418169965252055e-10 + p * w;\n        p = 1.5076572693500548083e-09 + p * w;\n        p = -3.7894654401267369937e-09 + p * w;\n        p = 7.6157012080783393804e-09 + p * w;\n        p = -1.4960026627149240478e-08 + p * w;\n        p = 2.9147953450901080826e-08 + p * w;\n        p = -6.7711997758452339498e-08 + p * w;\n        p = 2.2900482228026654717e-07 + p * w;\n        p = -9.9298272942317002539e-07 + p * w;\n        p = 4.5260625972231537039e-06 + p * w;\n        p = -1.9681778105531670567e-05 + p * w;\n        p = 7.5995277030017761139e-05 + p * w;\n        p = -0.00021503011930044477347 + p * w;\n        p = -0.00013871931833623122026 + p * w;\n        p = 1.0103004648645343977 + p * w;\n        p = 4.8499064014085844221 + p * w;\n    }\n    return p * x;\n}\n\ndouble standard_deviation(std::vector<double>::iterator first, std::vector<double>::iterator last) {\n    auto m = Catch::Benchmark::Detail::mean(first, last);\n    double variance = std::accumulate(first, last, 0.,\n                                      [m](double a, double b) {\n                                          double diff = b - m;\n                                          return a + diff * diff;\n                                      }) /\n                      (last - first);\n    return std::sqrt(variance);\n}\n\n}  // namespace\n\nnamespace Catch {\nnamespace Benchmark {\nnamespace Detail {\n\ndouble weighted_average_quantile(int k,\n                                 int q,\n                                 std::vector<double>::iterator first,\n                                 std::vector<double>::iterator last) {\n    auto count = last - first;\n    double idx = (count - 1) * k / static_cast<double>(q);\n    int j = static_cast<int>(idx);\n    double g = idx - j;\n    std::nth_element(first, first + j, last);\n    auto xj = first[j];\n    if (g == 0)\n        return xj;\n\n    auto xj1 = *std::min_element(first + (j + 1), last);\n    return xj + g * (xj1 - xj);\n}\n\ndouble erfc_inv(double x) { return erf_inv(1.0 - x); }\n\ndouble normal_quantile(double p) {\n    static const double ROOT_TWO = std::sqrt(2.0);\n\n    double result = 0.0;\n    assert(p >= 0 && p <= 1);\n    if (p < 0 || p > 1) {\n        return result;\n    }\n\n    result = -erfc_inv(2.0 * p);\n    // result *= normal distribution standard deviation (1.0) * sqrt(2)\n    result *= /*sd * */ ROOT_TWO;\n    // result += normal disttribution mean (0)\n    return result;\n}\n\ndouble outlier_variance(Estimate<double> mean, Estimate<double> stddev, int n) {\n    double sb = stddev.point;\n    double mn = mean.point / n;\n    double mg_min = mn / 2.;\n    double sg = (std::min)(mg_min / 4., sb / std::sqrt(n));\n    double sg2 = sg * sg;\n    double sb2 = sb * sb;\n\n    auto c_max = [n, mn, sb2, sg2](double x) -> double {\n        double k = mn - x;\n        double d = k * k;\n        double nd = n * d;\n        double k0 = -n * nd;\n        double k1 = sb2 - n * sg2 + nd;\n        double det = k1 * k1 - 4 * sg2 * k0;\n        return (int)(-2. * k0 / (k1 + std::sqrt(det)));\n    };\n\n    auto var_out = [n, sb2, sg2](double c) {\n        double nc = n - c;\n        return (nc / n) * (sb2 - nc * sg2);\n    };\n\n    return (std::min)(var_out(1), var_out((std::min)(c_max(0.), c_max(mg_min)))) / sb2;\n}\n\nbootstrap_analysis analyse_samples(double confidence_level,\n                                   int n_resamples,\n                                   std::vector<double>::iterator first,\n                                   std::vector<double>::iterator last) {\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS\n    static std::random_device entropy;\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n    auto n = static_cast<int>(\n            last - first);  // seriously, one can't use integral types without hell in C++\n\n    auto mean = &Detail::mean<std::vector<double>::iterator>;\n    auto stddev = &standard_deviation;\n\n#if defined(CATCH_CONFIG_USE_ASYNC)\n    auto Estimate = [=](double (*f)(std::vector<double>::iterator, std::vector<double>::iterator)) {\n        auto seed = entropy();\n        return std::async(std::launch::async, [=] {\n            std::mt19937 rng(seed);\n            auto resampled = resample(rng, n_resamples, first, last, f);\n            return bootstrap(confidence_level, first, last, resampled, f);\n        });\n    };\n\n    auto mean_future = Estimate(mean);\n    auto stddev_future = Estimate(stddev);\n\n    auto mean_estimate = mean_future.get();\n    auto stddev_estimate = stddev_future.get();\n#else\n    auto Estimate = [=](double (*f)(std::vector<double>::iterator, std::vector<double>::iterator)) {\n        auto seed = entropy();\n        std::mt19937 rng(seed);\n        auto resampled = resample(rng, n_resamples, first, last, f);\n        return bootstrap(confidence_level, first, last, resampled, f);\n    };\n\n    auto mean_estimate = Estimate(mean);\n    auto stddev_estimate = Estimate(stddev);\n#endif  // CATCH_USE_ASYNC\n\n    double outlier_variance = Detail::outlier_variance(mean_estimate, stddev_estimate, n);\n\n    return {mean_estimate, stddev_estimate, outlier_variance};\n}\n}  // namespace Detail\n}  // namespace Benchmark\n}  // namespace Catch\n\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n// end catch_stats.cpp\n// start catch_approx.cpp\n\n#include <cmath>\n#include <limits>\n\nnamespace {\n\n// Performs equivalent check of std::fabs(lhs - rhs) <= margin\n// But without the subtraction to allow for INFINITY in comparison\nbool marginComparison(double lhs, double rhs, double margin) {\n    return (lhs + margin >= rhs) && (rhs + margin >= lhs);\n}\n\n}  // namespace\n\nnamespace Catch {\nnamespace Detail {\n\nApprox::Approx(double value)\n        : m_epsilon(std::numeric_limits<float>::epsilon() * 100),\n          m_margin(0.0),\n          m_scale(0.0),\n          m_value(value) {}\n\nApprox Approx::custom() { return Approx(0); }\n\nApprox Approx::operator-() const {\n    auto temp(*this);\n    temp.m_value = -temp.m_value;\n    return temp;\n}\n\nstd::string Approx::toString() const {\n    ReusableStringStream rss;\n    rss << \"Approx( \" << ::Catch::Detail::stringify(m_value) << \" )\";\n    return rss.str();\n}\n\nbool Approx::equalityComparisonImpl(const double other) const {\n    // First try with fixed margin, then compute margin based on epsilon, scale\n    // and Approx's value Thanks to Richard Harris for his help refining the\n    // scaled margin value\n    return marginComparison(m_value, other, m_margin) ||\n           marginComparison(m_value, other,\n                            m_epsilon * (m_scale + std::fabs(std::isinf(m_value) ? 0 : m_value)));\n}\n\nvoid Approx::setMargin(double newMargin) {\n    CATCH_ENFORCE(newMargin >= 0,\n                  \"Invalid Approx::margin: \" << newMargin << '.'\n                                             << \" Approx::Margin has to be non-negative.\");\n    m_margin = newMargin;\n}\n\nvoid Approx::setEpsilon(double newEpsilon) {\n    CATCH_ENFORCE(newEpsilon >= 0 && newEpsilon <= 1.0,\n                  \"Invalid Approx::epsilon: \" << newEpsilon << '.'\n                                              << \" Approx::epsilon has to be in [0, 1]\");\n    m_epsilon = newEpsilon;\n}\n\n}  // end namespace Detail\n\nnamespace literals {\nDetail::Approx operator\"\" _a(long double val) { return Detail::Approx(val); }\nDetail::Approx operator\"\" _a(unsigned long long val) { return Detail::Approx(val); }\n}  // end namespace literals\n\nstd::string StringMaker<Catch::Detail::Approx>::convert(Catch::Detail::Approx const &value) {\n    return value.toString();\n}\n\n}  // end namespace Catch\n// end catch_approx.cpp\n// start catch_assertionhandler.cpp\n\n// start catch_debugger.h\n\nnamespace Catch {\nbool isDebuggerActive();\n}\n\n#ifdef CATCH_PLATFORM_MAC\n\n#if defined(__i386__) || defined(__x86_64__)\n#define CATCH_TRAP() __asm__(\"int $3\\n\" : :) /* NOLINT */\n#elif defined(__aarch64__)\n#define CATCH_TRAP() __asm__(\".inst 0xd4200000\")\n#endif\n\n#elif defined(CATCH_PLATFORM_IPHONE)\n\n// use inline assembler\n#if defined(__i386__) || defined(__x86_64__)\n#define CATCH_TRAP() __asm__(\"int $3\")\n#elif defined(__aarch64__)\n#define CATCH_TRAP() __asm__(\".inst 0xd4200000\")\n#elif defined(__arm__) && !defined(__thumb__)\n#define CATCH_TRAP() __asm__(\".inst 0xe7f001f0\")\n#elif defined(__arm__) && defined(__thumb__)\n#define CATCH_TRAP() __asm__(\".inst 0xde01\")\n#endif\n\n#elif defined(CATCH_PLATFORM_LINUX)\n// If we can use inline assembler, do it because this allows us to break\n// directly at the location of the failing check instead of breaking inside\n// raise() called from it, i.e. one stack frame below.\n#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))\n#define CATCH_TRAP() asm volatile(\"int $3\") /* NOLINT */\n#else                                       // Fall back to the generic way.\n#include <signal.h>\n\n#define CATCH_TRAP() raise(SIGTRAP)\n#endif\n#elif defined(_MSC_VER)\n#define CATCH_TRAP() __debugbreak()\n#elif defined(__MINGW32__)\nextern \"C\" __declspec(dllimport) void __stdcall DebugBreak();\n#define CATCH_TRAP() DebugBreak()\n#endif\n\n#ifndef CATCH_BREAK_INTO_DEBUGGER\n#ifdef CATCH_TRAP\n#define CATCH_BREAK_INTO_DEBUGGER()      \\\n    [] {                                 \\\n        if (Catch::isDebuggerActive()) { \\\n            CATCH_TRAP();                \\\n        }                                \\\n    }()\n#else\n#define CATCH_BREAK_INTO_DEBUGGER() [] {}()\n#endif\n#endif\n\n// end catch_debugger.h\n// start catch_run_context.h\n\n// start catch_fatal_condition.h\n\n#include <cassert>\n\nnamespace Catch {\n\n// Wrapper for platform-specific fatal error (signals/SEH) handlers\n//\n// Tries to be cooperative with other handlers, and not step over\n// other handlers. This means that unknown structured exceptions\n// are passed on, previous signal handlers are called, and so on.\n//\n// Can only be instantiated once, and assumes that once a signal\n// is caught, the binary will end up terminating. Thus, there\nclass FatalConditionHandler {\n    bool m_started = false;\n\n    // Install/disengage implementation for specific platform.\n    // Should be if-defed to work on current platform, can assume\n    // engage-disengage 1:1 pairing.\n    void engage_platform();\n    void disengage_platform();\n\npublic:\n    // Should also have platform-specific implementations as needed\n    FatalConditionHandler();\n    ~FatalConditionHandler();\n\n    void engage() {\n        assert(!m_started && \"Handler cannot be installed twice.\");\n        m_started = true;\n        engage_platform();\n    }\n\n    void disengage() {\n        assert(m_started && \"Handler cannot be uninstalled without being installed first\");\n        m_started = false;\n        disengage_platform();\n    }\n};\n\n//! Simple RAII guard for (dis)engaging the FatalConditionHandler\nclass FatalConditionHandlerGuard {\n    FatalConditionHandler *m_handler;\n\npublic:\n    FatalConditionHandlerGuard(FatalConditionHandler *handler) : m_handler(handler) {\n        m_handler->engage();\n    }\n    ~FatalConditionHandlerGuard() { m_handler->disengage(); }\n};\n\n}  // end namespace Catch\n\n// end catch_fatal_condition.h\n#include <string>\n\nnamespace Catch {\n\nstruct IMutableContext;\n\n///////////////////////////////////////////////////////////////////////////\n\nclass RunContext : public IResultCapture, public IRunner {\npublic:\n    RunContext(RunContext const &) = delete;\n    RunContext &operator=(RunContext const &) = delete;\n\n    explicit RunContext(IConfigPtr const &_config, IStreamingReporterPtr &&reporter);\n\n    ~RunContext() override;\n\n    void testGroupStarting(std::string const &testSpec,\n                           std::size_t groupIndex,\n                           std::size_t groupsCount);\n    void testGroupEnded(std::string const &testSpec,\n                        Totals const &totals,\n                        std::size_t groupIndex,\n                        std::size_t groupsCount);\n\n    Totals runTest(TestCase const &testCase);\n\n    IConfigPtr config() const;\n    IStreamingReporter &reporter() const;\n\npublic:  // IResultCapture\n    // Assertion handlers\n    void handleExpr(AssertionInfo const &info,\n                    ITransientExpression const &expr,\n                    AssertionReaction &reaction) override;\n    void handleMessage(AssertionInfo const &info,\n                       ResultWas::OfType resultType,\n                       StringRef const &message,\n                       AssertionReaction &reaction) override;\n    void handleUnexpectedExceptionNotThrown(AssertionInfo const &info,\n                                            AssertionReaction &reaction) override;\n    void handleUnexpectedInflightException(AssertionInfo const &info,\n                                           std::string const &message,\n                                           AssertionReaction &reaction) override;\n    void handleIncomplete(AssertionInfo const &info) override;\n    void handleNonExpr(AssertionInfo const &info,\n                       ResultWas::OfType resultType,\n                       AssertionReaction &reaction) override;\n\n    bool sectionStarted(SectionInfo const &sectionInfo, Counts &assertions) override;\n\n    void sectionEnded(SectionEndInfo const &endInfo) override;\n    void sectionEndedEarly(SectionEndInfo const &endInfo) override;\n\n    auto acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const &lineInfo)\n            -> IGeneratorTracker & override;\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n    void benchmarkPreparing(std::string const &name) override;\n    void benchmarkStarting(BenchmarkInfo const &info) override;\n    void benchmarkEnded(BenchmarkStats<> const &stats) override;\n    void benchmarkFailed(std::string const &error) override;\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    void pushScopedMessage(MessageInfo const &message) override;\n    void popScopedMessage(MessageInfo const &message) override;\n\n    void emplaceUnscopedMessage(MessageBuilder const &builder) override;\n\n    std::string getCurrentTestName() const override;\n\n    const AssertionResult *getLastResult() const override;\n\n    void exceptionEarlyReported() override;\n\n    void handleFatalErrorCondition(StringRef message) override;\n\n    bool lastAssertionPassed() override;\n\n    void assertionPassed() override;\n\npublic:\n    // !TBD We need to do this another way!\n    bool aborting() const final;\n\nprivate:\n    void runCurrentTest(std::string &redirectedCout, std::string &redirectedCerr);\n    void invokeActiveTestCase();\n\n    void resetAssertionInfo();\n    bool testForMissingAssertions(Counts &assertions);\n\n    void assertionEnded(AssertionResult const &result);\n    void reportExpr(AssertionInfo const &info,\n                    ResultWas::OfType resultType,\n                    ITransientExpression const *expr,\n                    bool negated);\n\n    void populateReaction(AssertionReaction &reaction);\n\nprivate:\n    void handleUnfinishedSections();\n\n    TestRunInfo m_runInfo;\n    IMutableContext &m_context;\n    TestCase const *m_activeTestCase = nullptr;\n    ITracker *m_testCaseTracker = nullptr;\n    Option<AssertionResult> m_lastResult;\n\n    IConfigPtr m_config;\n    Totals m_totals;\n    IStreamingReporterPtr m_reporter;\n    std::vector<MessageInfo> m_messages;\n    std::vector<ScopedMessage> m_messageScopes; /* Keeps owners of so-called unscoped messages. */\n    AssertionInfo m_lastAssertionInfo;\n    std::vector<SectionEndInfo> m_unfinishedSections;\n    std::vector<ITracker *> m_activeSections;\n    TrackerContext m_trackerContext;\n    FatalConditionHandler m_fatalConditionhandler;\n    bool m_lastAssertionPassed = false;\n    bool m_shouldReportUnexpected = true;\n    bool m_includeSuccessfulResults;\n};\n\nvoid seedRng(IConfig const &config);\nunsigned int rngSeed();\n}  // end namespace Catch\n\n// end catch_run_context.h\nnamespace Catch {\n\nnamespace {\nauto operator<<(std::ostream &os, ITransientExpression const &expr) -> std::ostream & {\n    expr.streamReconstructedExpression(os);\n    return os;\n}\n}  // namespace\n\nLazyExpression::LazyExpression(bool isNegated) : m_isNegated(isNegated) {}\n\nLazyExpression::LazyExpression(LazyExpression const &other) : m_isNegated(other.m_isNegated) {}\n\nLazyExpression::operator bool() const { return m_transientExpression != nullptr; }\n\nauto operator<<(std::ostream &os, LazyExpression const &lazyExpr) -> std::ostream & {\n    if (lazyExpr.m_isNegated)\n        os << \"!\";\n\n    if (lazyExpr) {\n        if (lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression())\n            os << \"(\" << *lazyExpr.m_transientExpression << \")\";\n        else\n            os << *lazyExpr.m_transientExpression;\n    } else {\n        os << \"{** error - unchecked empty expression requested **}\";\n    }\n    return os;\n}\n\nAssertionHandler::AssertionHandler(StringRef const &macroName,\n                                   SourceLineInfo const &lineInfo,\n                                   StringRef capturedExpression,\n                                   ResultDisposition::Flags resultDisposition)\n        : m_assertionInfo{macroName, lineInfo, capturedExpression, resultDisposition},\n          m_resultCapture(getResultCapture()) {}\n\nvoid AssertionHandler::handleExpr(ITransientExpression const &expr) {\n    m_resultCapture.handleExpr(m_assertionInfo, expr, m_reaction);\n}\nvoid AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const &message) {\n    m_resultCapture.handleMessage(m_assertionInfo, resultType, message, m_reaction);\n}\n\nauto AssertionHandler::allowThrows() const -> bool {\n    return getCurrentContext().getConfig()->allowThrows();\n}\n\nvoid AssertionHandler::complete() {\n    setCompleted();\n    if (m_reaction.shouldDebugBreak) {\n        // If you find your debugger stopping you here then go one level up on the\n        // call-stack for the code that caused it (typically a failed assertion)\n\n        // (To go back to the test and change execution, jump over the throw, next)\n        CATCH_BREAK_INTO_DEBUGGER();\n    }\n    if (m_reaction.shouldThrow) {\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n        throw Catch::TestFailureException();\n#else\n        CATCH_ERROR(\"Test failure requires aborting test!\");\n#endif\n    }\n}\nvoid AssertionHandler::setCompleted() { m_completed = true; }\n\nvoid AssertionHandler::handleUnexpectedInflightException() {\n    m_resultCapture.handleUnexpectedInflightException(\n            m_assertionInfo, Catch::translateActiveException(), m_reaction);\n}\n\nvoid AssertionHandler::handleExceptionThrownAsExpected() {\n    m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);\n}\nvoid AssertionHandler::handleExceptionNotThrownAsExpected() {\n    m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);\n}\n\nvoid AssertionHandler::handleUnexpectedExceptionNotThrown() {\n    m_resultCapture.handleUnexpectedExceptionNotThrown(m_assertionInfo, m_reaction);\n}\n\nvoid AssertionHandler::handleThrowingCallSkipped() {\n    m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);\n}\n\n// This is the overload that takes a string and infers the Equals matcher from\n// it The more general overload, that takes any string matcher, is in\n// catch_capture_matchers.cpp\nvoid handleExceptionMatchExpr(AssertionHandler &handler,\n                              std::string const &str,\n                              StringRef const &matcherString) {\n    handleExceptionMatchExpr(handler, Matchers::Equals(str), matcherString);\n}\n\n}  // namespace Catch\n// end catch_assertionhandler.cpp\n// start catch_assertionresult.cpp\n\nnamespace Catch {\nAssertionResultData::AssertionResultData(ResultWas::OfType _resultType,\n                                         LazyExpression const &_lazyExpression)\n        : lazyExpression(_lazyExpression), resultType(_resultType) {}\n\nstd::string AssertionResultData::reconstructExpression() const {\n    if (reconstructedExpression.empty()) {\n        if (lazyExpression) {\n            ReusableStringStream rss;\n            rss << lazyExpression;\n            reconstructedExpression = rss.str();\n        }\n    }\n    return reconstructedExpression;\n}\n\nAssertionResult::AssertionResult(AssertionInfo const &info, AssertionResultData const &data)\n        : m_info(info), m_resultData(data) {}\n\n// Result was a success\nbool AssertionResult::succeeded() const { return Catch::isOk(m_resultData.resultType); }\n\n// Result was a success, or failure is suppressed\nbool AssertionResult::isOk() const {\n    return Catch::isOk(m_resultData.resultType) || shouldSuppressFailure(m_info.resultDisposition);\n}\n\nResultWas::OfType AssertionResult::getResultType() const { return m_resultData.resultType; }\n\nbool AssertionResult::hasExpression() const { return !m_info.capturedExpression.empty(); }\n\nbool AssertionResult::hasMessage() const { return !m_resultData.message.empty(); }\n\nstd::string AssertionResult::getExpression() const {\n    // Possibly overallocating by 3 characters should be basically free\n    std::string expr;\n    expr.reserve(m_info.capturedExpression.size() + 3);\n    if (isFalseTest(m_info.resultDisposition)) {\n        expr += \"!(\";\n    }\n    expr += m_info.capturedExpression;\n    if (isFalseTest(m_info.resultDisposition)) {\n        expr += ')';\n    }\n    return expr;\n}\n\nstd::string AssertionResult::getExpressionInMacro() const {\n    std::string expr;\n    if (m_info.macroName.empty())\n        expr = static_cast<std::string>(m_info.capturedExpression);\n    else {\n        expr.reserve(m_info.macroName.size() + m_info.capturedExpression.size() + 4);\n        expr += m_info.macroName;\n        expr += \"( \";\n        expr += m_info.capturedExpression;\n        expr += \" )\";\n    }\n    return expr;\n}\n\nbool AssertionResult::hasExpandedExpression() const {\n    return hasExpression() && getExpandedExpression() != getExpression();\n}\n\nstd::string AssertionResult::getExpandedExpression() const {\n    std::string expr = m_resultData.reconstructExpression();\n    return expr.empty() ? getExpression() : expr;\n}\n\nstd::string AssertionResult::getMessage() const { return m_resultData.message; }\nSourceLineInfo AssertionResult::getSourceInfo() const { return m_info.lineInfo; }\n\nStringRef AssertionResult::getTestMacroName() const { return m_info.macroName; }\n\n}  // end namespace Catch\n// end catch_assertionresult.cpp\n// start catch_capture_matchers.cpp\n\nnamespace Catch {\n\nusing StringMatcher = Matchers::Impl::MatcherBase<std::string>;\n\n// This is the general overload that takes a any string matcher\n// There is another overload, in catch_assertionhandler.h/.cpp, that only takes\n// a string and infers the Equals matcher (so the header does not mention\n// matchers)\nvoid handleExceptionMatchExpr(AssertionHandler &handler,\n                              StringMatcher const &matcher,\n                              StringRef const &matcherString) {\n    std::string exceptionMessage = Catch::translateActiveException();\n    MatchExpr<std::string, StringMatcher const &> expr(exceptionMessage, matcher, matcherString);\n    handler.handleExpr(expr);\n}\n\n}  // namespace Catch\n// end catch_capture_matchers.cpp\n// start catch_commandline.cpp\n\n// start catch_commandline.h\n\n// start catch_clara.h\n\n// Use Catch's value for console width (store Clara's off to the side, if\n// present)\n#ifdef CLARA_CONFIG_CONSOLE_WIDTH\n#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH\n#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH\n#endif\n#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH - 1\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wweak-vtables\"\n#pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n#pragma clang diagnostic ignored \"-Wshadow\"\n#endif\n\n// start clara.hpp\n// Copyright 2017 Two Blue Cubes Ltd. All rights reserved.\n//\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n//\n// See https://github.com/philsquared/Clara for more details\n\n// Clara v1.1.5\n\n#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH\n#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80\n#endif\n\n#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH\n#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH\n#endif\n\n#ifndef CLARA_CONFIG_OPTIONAL_TYPE\n#ifdef __has_include\n#if __has_include(<optional>) && __cplusplus >= 201703L\n#include <optional>\n#define CLARA_CONFIG_OPTIONAL_TYPE std::optional\n#endif\n#endif\n#endif\n\n// ----------- #included from clara_textflow.hpp -----------\n\n// TextFlowCpp\n//\n// A single-header library for wrapping and laying out basic text, by Phil Nash\n//\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n//\n// This project is hosted at https://github.com/philsquared/textflowcpp\n\n#include <cassert>\n#include <ostream>\n#include <sstream>\n#include <vector>\n\n#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH\n#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80\n#endif\n\nnamespace Catch {\nnamespace clara {\nnamespace TextFlow {\n\ninline auto isWhitespace(char c) -> bool {\n    static std::string chars = \" \\t\\n\\r\";\n    return chars.find(c) != std::string::npos;\n}\ninline auto isBreakableBefore(char c) -> bool {\n    static std::string chars = \"[({<|\";\n    return chars.find(c) != std::string::npos;\n}\ninline auto isBreakableAfter(char c) -> bool {\n    static std::string chars = \"])}>.,:;*+-=&/\\\\\";\n    return chars.find(c) != std::string::npos;\n}\n\nclass Columns;\n\nclass Column {\n    std::vector<std::string> m_strings;\n    size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH;\n    size_t m_indent = 0;\n    size_t m_initialIndent = std::string::npos;\n\npublic:\n    class iterator {\n        friend Column;\n\n        Column const &m_column;\n        size_t m_stringIndex = 0;\n        size_t m_pos = 0;\n\n        size_t m_len = 0;\n        size_t m_end = 0;\n        bool m_suffix = false;\n\n        iterator(Column const &column, size_t stringIndex)\n                : m_column(column), m_stringIndex(stringIndex) {}\n\n        auto line() const -> std::string const & { return m_column.m_strings[m_stringIndex]; }\n\n        auto isBoundary(size_t at) const -> bool {\n            assert(at > 0);\n            assert(at <= line().size());\n\n            return at == line().size() ||\n                   (isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) ||\n                   isBreakableBefore(line()[at]) || isBreakableAfter(line()[at - 1]);\n        }\n\n        void calcLength() {\n            assert(m_stringIndex < m_column.m_strings.size());\n\n            m_suffix = false;\n            auto width = m_column.m_width - indent();\n            m_end = m_pos;\n            if (line()[m_pos] == '\\n') {\n                ++m_end;\n            }\n            while (m_end < line().size() && line()[m_end] != '\\n')\n                ++m_end;\n\n            if (m_end < m_pos + width) {\n                m_len = m_end - m_pos;\n            } else {\n                size_t len = width;\n                while (len > 0 && !isBoundary(m_pos + len))\n                    --len;\n                while (len > 0 && isWhitespace(line()[m_pos + len - 1]))\n                    --len;\n\n                if (len > 0) {\n                    m_len = len;\n                } else {\n                    m_suffix = true;\n                    m_len = width - 1;\n                }\n            }\n        }\n\n        auto indent() const -> size_t {\n            auto initial =\n                    m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos;\n            return initial == std::string::npos ? m_column.m_indent : initial;\n        }\n\n        auto addIndentAndSuffix(std::string const &plain) const -> std::string {\n            return std::string(indent(), ' ') + (m_suffix ? plain + \"-\" : plain);\n        }\n\n    public:\n        using difference_type = std::ptrdiff_t;\n        using value_type = std::string;\n        using pointer = value_type *;\n        using reference = value_type &;\n        using iterator_category = std::forward_iterator_tag;\n\n        explicit iterator(Column const &column) : m_column(column) {\n            assert(m_column.m_width > m_column.m_indent);\n            assert(m_column.m_initialIndent == std::string::npos ||\n                   m_column.m_width > m_column.m_initialIndent);\n            calcLength();\n            if (m_len == 0)\n                m_stringIndex++;  // Empty string\n        }\n\n        auto operator*() const -> std::string {\n            assert(m_stringIndex < m_column.m_strings.size());\n            assert(m_pos <= m_end);\n            return addIndentAndSuffix(line().substr(m_pos, m_len));\n        }\n\n        auto operator++() -> iterator & {\n            m_pos += m_len;\n            if (m_pos < line().size() && line()[m_pos] == '\\n')\n                m_pos += 1;\n            else\n                while (m_pos < line().size() && isWhitespace(line()[m_pos]))\n                    ++m_pos;\n\n            if (m_pos == line().size()) {\n                m_pos = 0;\n                ++m_stringIndex;\n            }\n            if (m_stringIndex < m_column.m_strings.size())\n                calcLength();\n            return *this;\n        }\n        auto operator++(int) -> iterator {\n            iterator prev(*this);\n            operator++();\n            return prev;\n        }\n\n        auto operator==(iterator const &other) const -> bool {\n            return m_pos == other.m_pos && m_stringIndex == other.m_stringIndex &&\n                   &m_column == &other.m_column;\n        }\n        auto operator!=(iterator const &other) const -> bool { return !operator==(other); }\n    };\n    using const_iterator = iterator;\n\n    explicit Column(std::string const &text) { m_strings.push_back(text); }\n\n    auto width(size_t newWidth) -> Column & {\n        assert(newWidth > 0);\n        m_width = newWidth;\n        return *this;\n    }\n    auto indent(size_t newIndent) -> Column & {\n        m_indent = newIndent;\n        return *this;\n    }\n    auto initialIndent(size_t newIndent) -> Column & {\n        m_initialIndent = newIndent;\n        return *this;\n    }\n\n    auto width() const -> size_t { return m_width; }\n    auto begin() const -> iterator { return iterator(*this); }\n    auto end() const -> iterator { return {*this, m_strings.size()}; }\n\n    inline friend std::ostream &operator<<(std::ostream &os, Column const &col) {\n        bool first = true;\n        for (auto line : col) {\n            if (first)\n                first = false;\n            else\n                os << \"\\n\";\n            os << line;\n        }\n        return os;\n    }\n\n    auto operator+(Column const &other) -> Columns;\n\n    auto toString() const -> std::string {\n        std::ostringstream oss;\n        oss << *this;\n        return oss.str();\n    }\n};\n\nclass Spacer : public Column {\npublic:\n    explicit Spacer(size_t spaceWidth) : Column(\"\") { width(spaceWidth); }\n};\n\nclass Columns {\n    std::vector<Column> m_columns;\n\npublic:\n    class iterator {\n        friend Columns;\n        struct EndTag {};\n\n        std::vector<Column> const &m_columns;\n        std::vector<Column::iterator> m_iterators;\n        size_t m_activeIterators;\n\n        iterator(Columns const &columns, EndTag)\n                : m_columns(columns.m_columns), m_activeIterators(0) {\n            m_iterators.reserve(m_columns.size());\n\n            for (auto const &col : m_columns)\n                m_iterators.push_back(col.end());\n        }\n\n    public:\n        using difference_type = std::ptrdiff_t;\n        using value_type = std::string;\n        using pointer = value_type *;\n        using reference = value_type &;\n        using iterator_category = std::forward_iterator_tag;\n\n        explicit iterator(Columns const &columns)\n                : m_columns(columns.m_columns), m_activeIterators(m_columns.size()) {\n            m_iterators.reserve(m_columns.size());\n\n            for (auto const &col : m_columns)\n                m_iterators.push_back(col.begin());\n        }\n\n        auto operator==(iterator const &other) const -> bool {\n            return m_iterators == other.m_iterators;\n        }\n        auto operator!=(iterator const &other) const -> bool {\n            return m_iterators != other.m_iterators;\n        }\n        auto operator*() const -> std::string {\n            std::string row, padding;\n\n            for (size_t i = 0; i < m_columns.size(); ++i) {\n                auto width = m_columns[i].width();\n                if (m_iterators[i] != m_columns[i].end()) {\n                    std::string col = *m_iterators[i];\n                    row += padding + col;\n                    if (col.size() < width)\n                        padding = std::string(width - col.size(), ' ');\n                    else\n                        padding = \"\";\n                } else {\n                    padding += std::string(width, ' ');\n                }\n            }\n            return row;\n        }\n        auto operator++() -> iterator & {\n            for (size_t i = 0; i < m_columns.size(); ++i) {\n                if (m_iterators[i] != m_columns[i].end())\n                    ++m_iterators[i];\n            }\n            return *this;\n        }\n        auto operator++(int) -> iterator {\n            iterator prev(*this);\n            operator++();\n            return prev;\n        }\n    };\n    using const_iterator = iterator;\n\n    auto begin() const -> iterator { return iterator(*this); }\n    auto end() const -> iterator { return {*this, iterator::EndTag()}; }\n\n    auto operator+=(Column const &col) -> Columns & {\n        m_columns.push_back(col);\n        return *this;\n    }\n    auto operator+(Column const &col) -> Columns {\n        Columns combined = *this;\n        combined += col;\n        return combined;\n    }\n\n    inline friend std::ostream &operator<<(std::ostream &os, Columns const &cols) {\n        bool first = true;\n        for (auto line : cols) {\n            if (first)\n                first = false;\n            else\n                os << \"\\n\";\n            os << line;\n        }\n        return os;\n    }\n\n    auto toString() const -> std::string {\n        std::ostringstream oss;\n        oss << *this;\n        return oss.str();\n    }\n};\n\ninline auto Column::operator+(Column const &other) -> Columns {\n    Columns cols;\n    cols += *this;\n    cols += other;\n    return cols;\n}\n}  // namespace TextFlow\n\n}  // namespace clara\n}  // namespace Catch\n\n// ----------- end of #include from clara_textflow.hpp -----------\n// ........... back in clara.hpp\n\n#include <algorithm>\n#include <cctype>\n#include <memory>\n#include <set>\n#include <string>\n\n#if !defined(CATCH_PLATFORM_WINDOWS) && \\\n        (defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER))\n#define CATCH_PLATFORM_WINDOWS\n#endif\n\nnamespace Catch {\nnamespace clara {\nnamespace detail {\n\n// Traits for extracting arg and return type of lambdas (for single argument\n// lambdas)\ntemplate <typename L>\nstruct UnaryLambdaTraits : UnaryLambdaTraits<decltype(&L::operator())> {};\n\ntemplate <typename ClassT, typename ReturnT, typename... Args>\nstruct UnaryLambdaTraits<ReturnT (ClassT::*)(Args...) const> {\n    static const bool isValid = false;\n};\n\ntemplate <typename ClassT, typename ReturnT, typename ArgT>\nstruct UnaryLambdaTraits<ReturnT (ClassT::*)(ArgT) const> {\n    static const bool isValid = true;\n    using ArgType = typename std::remove_const<typename std::remove_reference<ArgT>::type>::type;\n    using ReturnType = ReturnT;\n};\n\nclass TokenStream;\n\n// Transport for raw args (copied from main args, or supplied via init list for\n// testing)\nclass Args {\n    friend TokenStream;\n    std::string m_exeName;\n    std::vector<std::string> m_args;\n\npublic:\n    Args(int argc, char const *const *argv) : m_exeName(argv[0]), m_args(argv + 1, argv + argc) {}\n\n    Args(std::initializer_list<std::string> args)\n            : m_exeName(*args.begin()), m_args(args.begin() + 1, args.end()) {}\n\n    auto exeName() const -> std::string { return m_exeName; }\n};\n\n// Wraps a token coming from a token stream. These may not directly correspond\n// to strings as a single string may encode an option + its argument if the : or\n// = form is used\nenum class TokenType { Option, Argument };\nstruct Token {\n    TokenType type;\n    std::string token;\n};\n\ninline auto isOptPrefix(char c) -> bool {\n    return c == '-'\n#ifdef CATCH_PLATFORM_WINDOWS\n           || c == '/'\n#endif\n            ;\n}\n\n// Abstracts iterators into args as a stream of tokens, with option arguments\n// uniformly handled\nclass TokenStream {\n    using Iterator = std::vector<std::string>::const_iterator;\n    Iterator it;\n    Iterator itEnd;\n    std::vector<Token> m_tokenBuffer;\n\n    void loadBuffer() {\n        m_tokenBuffer.resize(0);\n\n        // Skip any empty strings\n        while (it != itEnd && it->empty())\n            ++it;\n\n        if (it != itEnd) {\n            auto const &next = *it;\n            if (isOptPrefix(next[0])) {\n                auto delimiterPos = next.find_first_of(\" :=\");\n                if (delimiterPos != std::string::npos) {\n                    m_tokenBuffer.push_back({TokenType::Option, next.substr(0, delimiterPos)});\n                    m_tokenBuffer.push_back({TokenType::Argument, next.substr(delimiterPos + 1)});\n                } else {\n                    if (next[1] != '-' && next.size() > 2) {\n                        std::string opt = \"- \";\n                        for (size_t i = 1; i < next.size(); ++i) {\n                            opt[1] = next[i];\n                            m_tokenBuffer.push_back({TokenType::Option, opt});\n                        }\n                    } else {\n                        m_tokenBuffer.push_back({TokenType::Option, next});\n                    }\n                }\n            } else {\n                m_tokenBuffer.push_back({TokenType::Argument, next});\n            }\n        }\n    }\n\npublic:\n    explicit TokenStream(Args const &args) : TokenStream(args.m_args.begin(), args.m_args.end()) {}\n\n    TokenStream(Iterator it, Iterator itEnd) : it(it), itEnd(itEnd) { loadBuffer(); }\n\n    explicit operator bool() const { return !m_tokenBuffer.empty() || it != itEnd; }\n\n    auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); }\n\n    auto operator*() const -> Token {\n        assert(!m_tokenBuffer.empty());\n        return m_tokenBuffer.front();\n    }\n\n    auto operator->() const -> Token const * {\n        assert(!m_tokenBuffer.empty());\n        return &m_tokenBuffer.front();\n    }\n\n    auto operator++() -> TokenStream & {\n        if (m_tokenBuffer.size() >= 2) {\n            m_tokenBuffer.erase(m_tokenBuffer.begin());\n        } else {\n            if (it != itEnd)\n                ++it;\n            loadBuffer();\n        }\n        return *this;\n    }\n};\n\nclass ResultBase {\npublic:\n    enum Type { Ok, LogicError, RuntimeError };\n\nprotected:\n    ResultBase(Type type) : m_type(type) {}\n    virtual ~ResultBase() = default;\n\n    virtual void enforceOk() const = 0;\n\n    Type m_type;\n};\n\ntemplate <typename T>\nclass ResultValueBase : public ResultBase {\npublic:\n    auto value() const -> T const & {\n        enforceOk();\n        return m_value;\n    }\n\nprotected:\n    ResultValueBase(Type type) : ResultBase(type) {}\n\n    ResultValueBase(ResultValueBase const &other) : ResultBase(other) {\n        if (m_type == ResultBase::Ok)\n            new (&m_value) T(other.m_value);\n    }\n\n    ResultValueBase(Type, T const &value) : ResultBase(Ok) { new (&m_value) T(value); }\n\n    auto operator=(ResultValueBase const &other) -> ResultValueBase & {\n        if (m_type == ResultBase::Ok)\n            m_value.~T();\n        ResultBase::operator=(other);\n        if (m_type == ResultBase::Ok)\n            new (&m_value) T(other.m_value);\n        return *this;\n    }\n\n    ~ResultValueBase() override {\n        if (m_type == Ok)\n            m_value.~T();\n    }\n\n    union {\n        T m_value;\n    };\n};\n\ntemplate <>\nclass ResultValueBase<void> : public ResultBase {\nprotected:\n    using ResultBase::ResultBase;\n};\n\ntemplate <typename T = void>\nclass BasicResult : public ResultValueBase<T> {\npublic:\n    template <typename U>\n    explicit BasicResult(BasicResult<U> const &other)\n            : ResultValueBase<T>(other.type()), m_errorMessage(other.errorMessage()) {\n        assert(type() != ResultBase::Ok);\n    }\n\n    template <typename U>\n    static auto ok(U const &value) -> BasicResult {\n        return {ResultBase::Ok, value};\n    }\n    static auto ok() -> BasicResult { return {ResultBase::Ok}; }\n    static auto logicError(std::string const &message) -> BasicResult {\n        return {ResultBase::LogicError, message};\n    }\n    static auto runtimeError(std::string const &message) -> BasicResult {\n        return {ResultBase::RuntimeError, message};\n    }\n\n    explicit operator bool() const { return m_type == ResultBase::Ok; }\n    auto type() const -> ResultBase::Type { return m_type; }\n    auto errorMessage() const -> std::string { return m_errorMessage; }\n\nprotected:\n    void enforceOk() const override {\n        // Errors shouldn't reach this point, but if they do\n        // the actual error message will be in m_errorMessage\n        assert(m_type != ResultBase::LogicError);\n        assert(m_type != ResultBase::RuntimeError);\n        if (m_type != ResultBase::Ok)\n            std::abort();\n    }\n\n    std::string m_errorMessage;  // Only populated if resultType is an error\n\n    BasicResult(ResultBase::Type type, std::string const &message)\n            : ResultValueBase<T>(type), m_errorMessage(message) {\n        assert(m_type != ResultBase::Ok);\n    }\n\n    using ResultValueBase<T>::ResultValueBase;\n    using ResultBase::m_type;\n};\n\nenum class ParseResultType { Matched, NoMatch, ShortCircuitAll, ShortCircuitSame };\n\nclass ParseState {\npublic:\n    ParseState(ParseResultType type, TokenStream const &remainingTokens)\n            : m_type(type), m_remainingTokens(remainingTokens) {}\n\n    auto type() const -> ParseResultType { return m_type; }\n    auto remainingTokens() const -> TokenStream { return m_remainingTokens; }\n\nprivate:\n    ParseResultType m_type;\n    TokenStream m_remainingTokens;\n};\n\nusing Result = BasicResult<void>;\nusing ParserResult = BasicResult<ParseResultType>;\nusing InternalParseResult = BasicResult<ParseState>;\n\nstruct HelpColumns {\n    std::string left;\n    std::string right;\n};\n\ntemplate <typename T>\ninline auto convertInto(std::string const &source, T &target) -> ParserResult {\n    std::stringstream ss;\n    ss << source;\n    ss >> target;\n    if (ss.fail())\n        return ParserResult::runtimeError(\"Unable to convert '\" + source + \"' to destination type\");\n    else\n        return ParserResult::ok(ParseResultType::Matched);\n}\ninline auto convertInto(std::string const &source, std::string &target) -> ParserResult {\n    target = source;\n    return ParserResult::ok(ParseResultType::Matched);\n}\ninline auto convertInto(std::string const &source, bool &target) -> ParserResult {\n    std::string srcLC = source;\n    std::transform(srcLC.begin(), srcLC.end(), srcLC.begin(),\n                   [](unsigned char c) { return static_cast<char>(std::tolower(c)); });\n    if (srcLC == \"y\" || srcLC == \"1\" || srcLC == \"true\" || srcLC == \"yes\" || srcLC == \"on\")\n        target = true;\n    else if (srcLC == \"n\" || srcLC == \"0\" || srcLC == \"false\" || srcLC == \"no\" || srcLC == \"off\")\n        target = false;\n    else\n        return ParserResult::runtimeError(\"Expected a boolean value but did not recognise: '\" +\n                                          source + \"'\");\n    return ParserResult::ok(ParseResultType::Matched);\n}\n#ifdef CLARA_CONFIG_OPTIONAL_TYPE\ntemplate <typename T>\ninline auto convertInto(std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE<T> &target)\n        -> ParserResult {\n    T temp;\n    auto result = convertInto(source, temp);\n    if (result)\n        target = std::move(temp);\n    return result;\n}\n#endif  // CLARA_CONFIG_OPTIONAL_TYPE\n\nstruct NonCopyable {\n    NonCopyable() = default;\n    NonCopyable(NonCopyable const &) = delete;\n    NonCopyable(NonCopyable &&) = delete;\n    NonCopyable &operator=(NonCopyable const &) = delete;\n    NonCopyable &operator=(NonCopyable &&) = delete;\n};\n\nstruct BoundRef : NonCopyable {\n    virtual ~BoundRef() = default;\n    virtual auto isContainer() const -> bool { return false; }\n    virtual auto isFlag() const -> bool { return false; }\n};\nstruct BoundValueRefBase : BoundRef {\n    virtual auto setValue(std::string const &arg) -> ParserResult = 0;\n};\nstruct BoundFlagRefBase : BoundRef {\n    virtual auto setFlag(bool flag) -> ParserResult = 0;\n    virtual auto isFlag() const -> bool { return true; }\n};\n\ntemplate <typename T>\nstruct BoundValueRef : BoundValueRefBase {\n    T &m_ref;\n\n    explicit BoundValueRef(T &ref) : m_ref(ref) {}\n\n    auto setValue(std::string const &arg) -> ParserResult override {\n        return convertInto(arg, m_ref);\n    }\n};\n\ntemplate <typename T>\nstruct BoundValueRef<std::vector<T>> : BoundValueRefBase {\n    std::vector<T> &m_ref;\n\n    explicit BoundValueRef(std::vector<T> &ref) : m_ref(ref) {}\n\n    auto isContainer() const -> bool override { return true; }\n\n    auto setValue(std::string const &arg) -> ParserResult override {\n        T temp;\n        auto result = convertInto(arg, temp);\n        if (result)\n            m_ref.push_back(temp);\n        return result;\n    }\n};\n\nstruct BoundFlagRef : BoundFlagRefBase {\n    bool &m_ref;\n\n    explicit BoundFlagRef(bool &ref) : m_ref(ref) {}\n\n    auto setFlag(bool flag) -> ParserResult override {\n        m_ref = flag;\n        return ParserResult::ok(ParseResultType::Matched);\n    }\n};\n\ntemplate <typename ReturnType>\nstruct LambdaInvoker {\n    static_assert(std::is_same<ReturnType, ParserResult>::value,\n                  \"Lambda must return void or clara::ParserResult\");\n\n    template <typename L, typename ArgType>\n    static auto invoke(L const &lambda, ArgType const &arg) -> ParserResult {\n        return lambda(arg);\n    }\n};\n\ntemplate <>\nstruct LambdaInvoker<void> {\n    template <typename L, typename ArgType>\n    static auto invoke(L const &lambda, ArgType const &arg) -> ParserResult {\n        lambda(arg);\n        return ParserResult::ok(ParseResultType::Matched);\n    }\n};\n\ntemplate <typename ArgType, typename L>\ninline auto invokeLambda(L const &lambda, std::string const &arg) -> ParserResult {\n    ArgType temp{};\n    auto result = convertInto(arg, temp);\n    return !result ? result\n                   : LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke(lambda, temp);\n}\n\ntemplate <typename L>\nstruct BoundLambda : BoundValueRefBase {\n    L m_lambda;\n\n    static_assert(UnaryLambdaTraits<L>::isValid, \"Supplied lambda must take exactly one argument\");\n    explicit BoundLambda(L const &lambda) : m_lambda(lambda) {}\n\n    auto setValue(std::string const &arg) -> ParserResult override {\n        return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>(m_lambda, arg);\n    }\n};\n\ntemplate <typename L>\nstruct BoundFlagLambda : BoundFlagRefBase {\n    L m_lambda;\n\n    static_assert(UnaryLambdaTraits<L>::isValid, \"Supplied lambda must take exactly one argument\");\n    static_assert(std::is_same<typename UnaryLambdaTraits<L>::ArgType, bool>::value,\n                  \"flags must be boolean\");\n\n    explicit BoundFlagLambda(L const &lambda) : m_lambda(lambda) {}\n\n    auto setFlag(bool flag) -> ParserResult override {\n        return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke(m_lambda, flag);\n    }\n};\n\nenum class Optionality { Optional, Required };\n\nstruct Parser;\n\nclass ParserBase {\npublic:\n    virtual ~ParserBase() = default;\n    virtual auto validate() const -> Result { return Result::ok(); }\n    virtual auto parse(std::string const &exeName, TokenStream const &tokens) const\n            -> InternalParseResult = 0;\n    virtual auto cardinality() const -> size_t { return 1; }\n\n    auto parse(Args const &args) const -> InternalParseResult {\n        return parse(args.exeName(), TokenStream(args));\n    }\n};\n\ntemplate <typename DerivedT>\nclass ComposableParserImpl : public ParserBase {\npublic:\n    template <typename T>\n    auto operator|(T const &other) const -> Parser;\n\n    template <typename T>\n    auto operator+(T const &other) const -> Parser;\n};\n\n// Common code and state for Args and Opts\ntemplate <typename DerivedT>\nclass ParserRefImpl : public ComposableParserImpl<DerivedT> {\nprotected:\n    Optionality m_optionality = Optionality::Optional;\n    std::shared_ptr<BoundRef> m_ref;\n    std::string m_hint;\n    std::string m_description;\n\n    explicit ParserRefImpl(std::shared_ptr<BoundRef> const &ref) : m_ref(ref) {}\n\npublic:\n    template <typename T>\n    ParserRefImpl(T &ref, std::string const &hint)\n            : m_ref(std::make_shared<BoundValueRef<T>>(ref)), m_hint(hint) {}\n\n    template <typename LambdaT>\n    ParserRefImpl(LambdaT const &ref, std::string const &hint)\n            : m_ref(std::make_shared<BoundLambda<LambdaT>>(ref)), m_hint(hint) {}\n\n    auto operator()(std::string const &description) -> DerivedT & {\n        m_description = description;\n        return static_cast<DerivedT &>(*this);\n    }\n\n    auto optional() -> DerivedT & {\n        m_optionality = Optionality::Optional;\n        return static_cast<DerivedT &>(*this);\n    };\n\n    auto required() -> DerivedT & {\n        m_optionality = Optionality::Required;\n        return static_cast<DerivedT &>(*this);\n    };\n\n    auto isOptional() const -> bool { return m_optionality == Optionality::Optional; }\n\n    auto cardinality() const -> size_t override {\n        if (m_ref->isContainer())\n            return 0;\n        else\n            return 1;\n    }\n\n    auto hint() const -> std::string { return m_hint; }\n};\n\nclass ExeName : public ComposableParserImpl<ExeName> {\n    std::shared_ptr<std::string> m_name;\n    std::shared_ptr<BoundValueRefBase> m_ref;\n\n    template <typename LambdaT>\n    static auto makeRef(LambdaT const &lambda) -> std::shared_ptr<BoundValueRefBase> {\n        return std::make_shared<BoundLambda<LambdaT>>(lambda);\n    }\n\npublic:\n    ExeName() : m_name(std::make_shared<std::string>(\"<executable>\")) {}\n\n    explicit ExeName(std::string &ref) : ExeName() {\n        m_ref = std::make_shared<BoundValueRef<std::string>>(ref);\n    }\n\n    template <typename LambdaT>\n    explicit ExeName(LambdaT const &lambda) : ExeName() {\n        m_ref = std::make_shared<BoundLambda<LambdaT>>(lambda);\n    }\n\n    // The exe name is not parsed out of the normal tokens, but is handled\n    // specially\n    auto parse(std::string const &, TokenStream const &tokens) const\n            -> InternalParseResult override {\n        return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens));\n    }\n\n    auto name() const -> std::string { return *m_name; }\n    auto set(std::string const &newName) -> ParserResult {\n        auto lastSlash = newName.find_last_of(\"\\\\/\");\n        auto filename = (lastSlash == std::string::npos) ? newName : newName.substr(lastSlash + 1);\n\n        *m_name = filename;\n        if (m_ref)\n            return m_ref->setValue(filename);\n        else\n            return ParserResult::ok(ParseResultType::Matched);\n    }\n};\n\nclass Arg : public ParserRefImpl<Arg> {\npublic:\n    using ParserRefImpl::ParserRefImpl;\n\n    auto parse(std::string const &, TokenStream const &tokens) const\n            -> InternalParseResult override {\n        auto validationResult = validate();\n        if (!validationResult)\n            return InternalParseResult(validationResult);\n\n        auto remainingTokens = tokens;\n        auto const &token = *remainingTokens;\n        if (token.type != TokenType::Argument)\n            return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens));\n\n        assert(!m_ref->isFlag());\n        auto valueRef = static_cast<detail::BoundValueRefBase *>(m_ref.get());\n\n        auto result = valueRef->setValue(remainingTokens->token);\n        if (!result)\n            return InternalParseResult(result);\n        else\n            return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens));\n    }\n};\n\ninline auto normaliseOpt(std::string const &optName) -> std::string {\n#ifdef CATCH_PLATFORM_WINDOWS\n    if (optName[0] == '/')\n        return \"-\" + optName.substr(1);\n    else\n#endif\n        return optName;\n}\n\nclass Opt : public ParserRefImpl<Opt> {\nprotected:\n    std::vector<std::string> m_optNames;\n\npublic:\n    template <typename LambdaT>\n    explicit Opt(LambdaT const &ref)\n            : ParserRefImpl(std::make_shared<BoundFlagLambda<LambdaT>>(ref)) {}\n\n    explicit Opt(bool &ref) : ParserRefImpl(std::make_shared<BoundFlagRef>(ref)) {}\n\n    template <typename LambdaT>\n    Opt(LambdaT const &ref, std::string const &hint) : ParserRefImpl(ref, hint) {}\n\n    template <typename T>\n    Opt(T &ref, std::string const &hint) : ParserRefImpl(ref, hint) {}\n\n    auto operator[](std::string const &optName) -> Opt & {\n        m_optNames.push_back(optName);\n        return *this;\n    }\n\n    auto getHelpColumns() const -> std::vector<HelpColumns> {\n        std::ostringstream oss;\n        bool first = true;\n        for (auto const &opt : m_optNames) {\n            if (first)\n                first = false;\n            else\n                oss << \", \";\n            oss << opt;\n        }\n        if (!m_hint.empty())\n            oss << \" <\" << m_hint << \">\";\n        return {{oss.str(), m_description}};\n    }\n\n    auto isMatch(std::string const &optToken) const -> bool {\n        auto normalisedToken = normaliseOpt(optToken);\n        for (auto const &name : m_optNames) {\n            if (normaliseOpt(name) == normalisedToken)\n                return true;\n        }\n        return false;\n    }\n\n    using ParserBase::parse;\n\n    auto parse(std::string const &, TokenStream const &tokens) const\n            -> InternalParseResult override {\n        auto validationResult = validate();\n        if (!validationResult)\n            return InternalParseResult(validationResult);\n\n        auto remainingTokens = tokens;\n        if (remainingTokens && remainingTokens->type == TokenType::Option) {\n            auto const &token = *remainingTokens;\n            if (isMatch(token.token)) {\n                if (m_ref->isFlag()) {\n                    auto flagRef = static_cast<detail::BoundFlagRefBase *>(m_ref.get());\n                    auto result = flagRef->setFlag(true);\n                    if (!result)\n                        return InternalParseResult(result);\n                    if (result.value() == ParseResultType::ShortCircuitAll)\n                        return InternalParseResult::ok(ParseState(result.value(), remainingTokens));\n                } else {\n                    auto valueRef = static_cast<detail::BoundValueRefBase *>(m_ref.get());\n                    ++remainingTokens;\n                    if (!remainingTokens)\n                        return InternalParseResult::runtimeError(\"Expected argument following \" +\n                                                                 token.token);\n                    auto const &argToken = *remainingTokens;\n                    if (argToken.type != TokenType::Argument)\n                        return InternalParseResult::runtimeError(\"Expected argument following \" +\n                                                                 token.token);\n                    auto result = valueRef->setValue(argToken.token);\n                    if (!result)\n                        return InternalParseResult(result);\n                    if (result.value() == ParseResultType::ShortCircuitAll)\n                        return InternalParseResult::ok(ParseState(result.value(), remainingTokens));\n                }\n                return InternalParseResult::ok(\n                        ParseState(ParseResultType::Matched, ++remainingTokens));\n            }\n        }\n        return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens));\n    }\n\n    auto validate() const -> Result override {\n        if (m_optNames.empty())\n            return Result::logicError(\"No options supplied to Opt\");\n        for (auto const &name : m_optNames) {\n            if (name.empty())\n                return Result::logicError(\"Option name cannot be empty\");\n#ifdef CATCH_PLATFORM_WINDOWS\n            if (name[0] != '-' && name[0] != '/')\n                return Result::logicError(\"Option name must begin with '-' or '/'\");\n#else\n            if (name[0] != '-')\n                return Result::logicError(\"Option name must begin with '-'\");\n#endif\n        }\n        return ParserRefImpl::validate();\n    }\n};\n\nstruct Help : Opt {\n    Help(bool &showHelpFlag)\n            : Opt([&](bool flag) {\n                  showHelpFlag = flag;\n                  return ParserResult::ok(ParseResultType::ShortCircuitAll);\n              }) {\n        static_cast<Opt &> (*this)(\"display usage information\")[\"-?\"][\"-h\"][\"--help\"].optional();\n    }\n};\n\nstruct Parser : ParserBase {\n    mutable ExeName m_exeName;\n    std::vector<Opt> m_options;\n    std::vector<Arg> m_args;\n\n    auto operator|=(ExeName const &exeName) -> Parser & {\n        m_exeName = exeName;\n        return *this;\n    }\n\n    auto operator|=(Arg const &arg) -> Parser & {\n        m_args.push_back(arg);\n        return *this;\n    }\n\n    auto operator|=(Opt const &opt) -> Parser & {\n        m_options.push_back(opt);\n        return *this;\n    }\n\n    auto operator|=(Parser const &other) -> Parser & {\n        m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end());\n        m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end());\n        return *this;\n    }\n\n    template <typename T>\n    auto operator|(T const &other) const -> Parser {\n        return Parser(*this) |= other;\n    }\n\n    // Forward deprecated interface with '+' instead of '|'\n    template <typename T>\n    auto operator+=(T const &other) -> Parser & {\n        return operator|=(other);\n    }\n    template <typename T>\n    auto operator+(T const &other) const -> Parser {\n        return operator|(other);\n    }\n\n    auto getHelpColumns() const -> std::vector<HelpColumns> {\n        std::vector<HelpColumns> cols;\n        for (auto const &o : m_options) {\n            auto childCols = o.getHelpColumns();\n            cols.insert(cols.end(), childCols.begin(), childCols.end());\n        }\n        return cols;\n    }\n\n    void writeToStream(std::ostream &os) const {\n        if (!m_exeName.name().empty()) {\n            os << \"usage:\\n\"\n               << \"  \" << m_exeName.name() << \" \";\n            bool required = true, first = true;\n            for (auto const &arg : m_args) {\n                if (first)\n                    first = false;\n                else\n                    os << \" \";\n                if (arg.isOptional() && required) {\n                    os << \"[\";\n                    required = false;\n                }\n                os << \"<\" << arg.hint() << \">\";\n                if (arg.cardinality() == 0)\n                    os << \" ... \";\n            }\n            if (!required)\n                os << \"]\";\n            if (!m_options.empty())\n                os << \" options\";\n            os << \"\\n\\nwhere options are:\" << std::endl;\n        }\n\n        auto rows = getHelpColumns();\n        size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH;\n        size_t optWidth = 0;\n        for (auto const &cols : rows)\n            optWidth = (std::max)(optWidth, cols.left.size() + 2);\n\n        optWidth = (std::min)(optWidth, consoleWidth / 2);\n\n        for (auto const &cols : rows) {\n            auto row = TextFlow::Column(cols.left).width(optWidth).indent(2) + TextFlow::Spacer(4) +\n                       TextFlow::Column(cols.right).width(consoleWidth - 7 - optWidth);\n            os << row << std::endl;\n        }\n    }\n\n    friend auto operator<<(std::ostream &os, Parser const &parser) -> std::ostream & {\n        parser.writeToStream(os);\n        return os;\n    }\n\n    auto validate() const -> Result override {\n        for (auto const &opt : m_options) {\n            auto result = opt.validate();\n            if (!result)\n                return result;\n        }\n        for (auto const &arg : m_args) {\n            auto result = arg.validate();\n            if (!result)\n                return result;\n        }\n        return Result::ok();\n    }\n\n    using ParserBase::parse;\n\n    auto parse(std::string const &exeName, TokenStream const &tokens) const\n            -> InternalParseResult override {\n        struct ParserInfo {\n            ParserBase const *parser = nullptr;\n            size_t count = 0;\n        };\n        const size_t totalParsers = m_options.size() + m_args.size();\n        assert(totalParsers < 512);\n        // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want\n        // to do\n        ParserInfo parseInfos[512];\n\n        {\n            size_t i = 0;\n            for (auto const &opt : m_options)\n                parseInfos[i++].parser = &opt;\n            for (auto const &arg : m_args)\n                parseInfos[i++].parser = &arg;\n        }\n\n        m_exeName.set(exeName);\n\n        auto result = InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens));\n        while (result.value().remainingTokens()) {\n            bool tokenParsed = false;\n\n            for (size_t i = 0; i < totalParsers; ++i) {\n                auto &parseInfo = parseInfos[i];\n                if (parseInfo.parser->cardinality() == 0 ||\n                    parseInfo.count < parseInfo.parser->cardinality()) {\n                    result = parseInfo.parser->parse(exeName, result.value().remainingTokens());\n                    if (!result)\n                        return result;\n                    if (result.value().type() != ParseResultType::NoMatch) {\n                        tokenParsed = true;\n                        ++parseInfo.count;\n                        break;\n                    }\n                }\n            }\n\n            if (result.value().type() == ParseResultType::ShortCircuitAll)\n                return result;\n            if (!tokenParsed)\n                return InternalParseResult::runtimeError(\"Unrecognised token: \" +\n                                                         result.value().remainingTokens()->token);\n        }\n        // !TBD Check missing required options\n        return result;\n    }\n};\n\ntemplate <typename DerivedT>\ntemplate <typename T>\nauto ComposableParserImpl<DerivedT>::operator|(T const &other) const -> Parser {\n    return Parser() | static_cast<DerivedT const &>(*this) | other;\n}\n}  // namespace detail\n\n// A Combined parser\nusing detail::Parser;\n\n// A parser for options\nusing detail::Opt;\n\n// A parser for arguments\nusing detail::Arg;\n\n// Wrapper for argc, argv from main()\nusing detail::Args;\n\n// Specifies the name of the executable\nusing detail::ExeName;\n\n// Convenience wrapper for option parser that specifies the help option\nusing detail::Help;\n\n// enum of result types from a parse\nusing detail::ParseResultType;\n\n// Result type for parser operation\nusing detail::ParserResult;\n\n}  // namespace clara\n}  // namespace Catch\n\n// end clara.hpp\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n// Restore Clara's value for console width, if present\n#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH\n#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH\n#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH\n#endif\n\n// end catch_clara.h\nnamespace Catch {\n\nclara::Parser makeCommandLineParser(ConfigData &config);\n\n}  // end namespace Catch\n\n// end catch_commandline.h\n#include <ctime>\n#include <fstream>\n\nnamespace Catch {\n\nclara::Parser makeCommandLineParser(ConfigData &config) {\n    using namespace clara;\n\n    auto const setWarning = [&](std::string const &warning) {\n        auto warningSet = [&]() {\n            if (warning == \"NoAssertions\")\n                return WarnAbout::NoAssertions;\n\n            if (warning == \"NoTests\")\n                return WarnAbout::NoTests;\n\n            return WarnAbout::Nothing;\n        }();\n\n        if (warningSet == WarnAbout::Nothing)\n            return ParserResult::runtimeError(\"Unrecognised warning: '\" + warning + \"'\");\n        config.warnings = static_cast<WarnAbout::What>(config.warnings | warningSet);\n        return ParserResult::ok(ParseResultType::Matched);\n    };\n    auto const loadTestNamesFromFile = [&](std::string const &filename) {\n        std::ifstream f(filename.c_str());\n        if (!f.is_open())\n            return ParserResult::runtimeError(\"Unable to load input file: '\" + filename + \"'\");\n\n        std::string line;\n        while (std::getline(f, line)) {\n            line = trim(line);\n            if (!line.empty() && !startsWith(line, '#')) {\n                if (!startsWith(line, '\"'))\n                    line = '\"' + line + '\"';\n                config.testsOrTags.push_back(line);\n                config.testsOrTags.emplace_back(\",\");\n            }\n        }\n        // Remove comma in the end\n        if (!config.testsOrTags.empty())\n            config.testsOrTags.erase(config.testsOrTags.end() - 1);\n\n        return ParserResult::ok(ParseResultType::Matched);\n    };\n    auto const setTestOrder = [&](std::string const &order) {\n        if (startsWith(\"declared\", order))\n            config.runOrder = RunTests::InDeclarationOrder;\n        else if (startsWith(\"lexical\", order))\n            config.runOrder = RunTests::InLexicographicalOrder;\n        else if (startsWith(\"random\", order))\n            config.runOrder = RunTests::InRandomOrder;\n        else\n            return clara::ParserResult::runtimeError(\"Unrecognised ordering: '\" + order + \"'\");\n        return ParserResult::ok(ParseResultType::Matched);\n    };\n    auto const setRngSeed = [&](std::string const &seed) {\n        if (seed != \"time\")\n            return clara::detail::convertInto(seed, config.rngSeed);\n        config.rngSeed = static_cast<unsigned int>(std::time(nullptr));\n        return ParserResult::ok(ParseResultType::Matched);\n    };\n    auto const setColourUsage = [&](std::string const &useColour) {\n        auto mode = toLower(useColour);\n\n        if (mode == \"yes\")\n            config.useColour = UseColour::Yes;\n        else if (mode == \"no\")\n            config.useColour = UseColour::No;\n        else if (mode == \"auto\")\n            config.useColour = UseColour::Auto;\n        else\n            return ParserResult::runtimeError(\"colour mode must be one of: auto, yes or no. '\" +\n                                              useColour + \"' not recognised\");\n        return ParserResult::ok(ParseResultType::Matched);\n    };\n    auto const setWaitForKeypress = [&](std::string const &keypress) {\n        auto keypressLc = toLower(keypress);\n        if (keypressLc == \"never\")\n            config.waitForKeypress = WaitForKeypress::Never;\n        else if (keypressLc == \"start\")\n            config.waitForKeypress = WaitForKeypress::BeforeStart;\n        else if (keypressLc == \"exit\")\n            config.waitForKeypress = WaitForKeypress::BeforeExit;\n        else if (keypressLc == \"both\")\n            config.waitForKeypress = WaitForKeypress::BeforeStartAndExit;\n        else\n            return ParserResult::runtimeError(\n                    \"keypress argument must be one of: never, start, exit or both. '\" + keypress +\n                    \"' not recognised\");\n        return ParserResult::ok(ParseResultType::Matched);\n    };\n    auto const setVerbosity = [&](std::string const &verbosity) {\n        auto lcVerbosity = toLower(verbosity);\n        if (lcVerbosity == \"quiet\")\n            config.verbosity = Verbosity::Quiet;\n        else if (lcVerbosity == \"normal\")\n            config.verbosity = Verbosity::Normal;\n        else if (lcVerbosity == \"high\")\n            config.verbosity = Verbosity::High;\n        else\n            return ParserResult::runtimeError(\"Unrecognised verbosity, '\" + verbosity + \"'\");\n        return ParserResult::ok(ParseResultType::Matched);\n    };\n    auto const setReporter = [&](std::string const &reporter) {\n        IReporterRegistry::FactoryMap const &factories =\n                getRegistryHub().getReporterRegistry().getFactories();\n\n        auto lcReporter = toLower(reporter);\n        auto result = factories.find(lcReporter);\n\n        if (factories.end() != result)\n            config.reporterName = lcReporter;\n        else\n            return ParserResult::runtimeError(\"Unrecognized reporter, '\" + reporter +\n                                              \"'. Check available with --list-reporters\");\n        return ParserResult::ok(ParseResultType::Matched);\n    };\n\n    auto cli =\n            ExeName(config.processName) | Help(config.showHelp) |\n            Opt(config.listTests)[\"-l\"][\"--list-tests\"](\"list all/matching test cases\") |\n            Opt(config.listTags)[\"-t\"][\"--list-tags\"](\"list all/matching tags\") |\n            Opt(config.showSuccessfulTests)[\"-s\"][\"--success\"](\n                    \"include successful tests in output\") |\n            Opt(config.shouldDebugBreak)[\"-b\"][\"--break\"](\"break into debugger on failure\") |\n            Opt(config.noThrow)[\"-e\"][\"--nothrow\"](\"skip exception tests\") |\n            Opt(config.showInvisibles)[\"-i\"][\"--invisibles\"](\"show invisibles (tabs, newlines)\") |\n            Opt(config.outputFilename, \"filename\")[\"-o\"][\"--out\"](\"output filename\") |\n            Opt(setReporter, \"name\")[\"-r\"][\"--reporter\"](\"reporter to use (defaults to console)\") |\n            Opt(config.name, \"name\")[\"-n\"][\"--name\"](\"suite name\") |\n            Opt([&](bool) { config.abortAfter = 1; })[\"-a\"][\"--abort\"](\"abort at first failure\") |\n            Opt([&](int x) { config.abortAfter = x; },\n                \"no. failures\")[\"-x\"][\"--abortx\"](\"abort after x failures\") |\n            Opt(setWarning, \"warning name\")[\"-w\"][\"--warn\"](\"enable warnings\") |\n            Opt(\n                    [&](bool flag) {\n                        config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never;\n                    },\n                    \"yes|no\")[\"-d\"][\"--durations\"](\"show test durations\") |\n            Opt(config.minDuration, \"seconds\")[\"-D\"][\"--min-duration\"](\n                    \"show test durations for tests taking at least the given number of \"\n                    \"seconds\") |\n            Opt(loadTestNamesFromFile,\n                \"filename\")[\"-f\"][\"--input-file\"](\"load test names to run from a file\") |\n            Opt(config.filenamesAsTags)[\"-#\"][\"--filenames-as-tags\"](\n                    \"adds a tag for the filename\") |\n            Opt(config.sectionsToRun, \"section name\")[\"-c\"][\"--section\"](\"specify section to run\") |\n            Opt(setVerbosity, \"quiet|normal|high\")[\"-v\"][\"--verbosity\"](\"set output verbosity\") |\n            Opt(config.listTestNamesOnly)[\"--list-test-names-only\"](\n                    \"list all/matching test cases names only\") |\n            Opt(config.listReporters)[\"--list-reporters\"](\"list all reporters\") |\n            Opt(setTestOrder, \"decl|lex|rand\")[\"--order\"](\"test case order (defaults to decl)\") |\n            Opt(setRngSeed,\n                \"'time'|number\")[\"--rng-seed\"](\"set a specific seed for random numbers\") |\n            Opt(setColourUsage, \"yes|no\")[\"--use-colour\"](\"should output be colourised\") |\n            Opt(config.libIdentify)[\"--libidentify\"](\n                    \"report name and version according to libidentify standard\") |\n            Opt(setWaitForKeypress, \"never|start|exit|both\")[\"--wait-for-keypress\"](\n                    \"waits for a keypress before exiting\") |\n            Opt(config.benchmarkSamples,\n                \"samples\")[\"--benchmark-samples\"](\"number of samples to collect (default: 100)\") |\n            Opt(config.benchmarkResamples, \"resamples\")[\"--benchmark-resamples\"](\n                    \"number of resamples for the bootstrap (default: 100000)\") |\n            Opt(config.benchmarkConfidenceInterval,\n                \"confidence interval\")[\"--benchmark-confidence-interval\"](\n                    \"confidence interval for the bootstrap (between 0 and 1, default: \"\n                    \"0.95)\") |\n            Opt(config.benchmarkNoAnalysis)[\"--benchmark-no-analysis\"](\n                    \"perform only measurements; do not perform any analysis\") |\n            Opt(config.benchmarkWarmupTime, \"benchmarkWarmupTime\")[\"--benchmark-warmup-time\"](\n                    \"amount of time in milliseconds spent on warming up each test \"\n                    \"(default: 100)\") |\n            Arg(config.testsOrTags, \"test name|pattern|tags\")(\"which test or tests to use\");\n\n    return cli;\n}\n\n}  // end namespace Catch\n// end catch_commandline.cpp\n// start catch_common.cpp\n\n#include <cstring>\n#include <ostream>\n\nnamespace Catch {\n\nbool SourceLineInfo::operator==(SourceLineInfo const &other) const noexcept {\n    return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0);\n}\nbool SourceLineInfo::operator<(SourceLineInfo const &other) const noexcept {\n    // We can assume that the same file will usually have the same pointer.\n    // Thus, if the pointers are the same, there is no point in calling the strcmp\n    return line < other.line ||\n           (line == other.line && file != other.file && (std::strcmp(file, other.file) < 0));\n}\n\nstd::ostream &operator<<(std::ostream &os, SourceLineInfo const &info) {\n#ifndef __GNUG__\n    os << info.file << '(' << info.line << ')';\n#else\n    os << info.file << ':' << info.line;\n#endif\n    return os;\n}\n\nstd::string StreamEndStop::operator+() const { return std::string(); }\n\nNonCopyable::NonCopyable() = default;\nNonCopyable::~NonCopyable() = default;\n\n}  // namespace Catch\n// end catch_common.cpp\n// start catch_config.cpp\n\nnamespace Catch {\n\nConfig::Config(ConfigData const &data) : m_data(data), m_stream(openStream()) {\n    // We need to trim filter specs to avoid trouble with superfluous\n    // whitespace (esp. important for bdd macros, as those are manually\n    // aligned with whitespace).\n\n    for (auto &elem : m_data.testsOrTags) {\n        elem = trim(elem);\n    }\n    for (auto &elem : m_data.sectionsToRun) {\n        elem = trim(elem);\n    }\n\n    TestSpecParser parser(ITagAliasRegistry::get());\n    if (!m_data.testsOrTags.empty()) {\n        m_hasTestFilters = true;\n        for (auto const &testOrTags : m_data.testsOrTags) {\n            parser.parse(testOrTags);\n        }\n    }\n    m_testSpec = parser.testSpec();\n}\n\nstd::string const &Config::getFilename() const { return m_data.outputFilename; }\n\nbool Config::listTests() const { return m_data.listTests; }\nbool Config::listTestNamesOnly() const { return m_data.listTestNamesOnly; }\nbool Config::listTags() const { return m_data.listTags; }\nbool Config::listReporters() const { return m_data.listReporters; }\n\nstd::string Config::getProcessName() const { return m_data.processName; }\nstd::string const &Config::getReporterName() const { return m_data.reporterName; }\n\nstd::vector<std::string> const &Config::getTestsOrTags() const { return m_data.testsOrTags; }\nstd::vector<std::string> const &Config::getSectionsToRun() const { return m_data.sectionsToRun; }\n\nTestSpec const &Config::testSpec() const { return m_testSpec; }\nbool Config::hasTestFilters() const { return m_hasTestFilters; }\n\nbool Config::showHelp() const { return m_data.showHelp; }\n\n// IConfig interface\nbool Config::allowThrows() const { return !m_data.noThrow; }\nstd::ostream &Config::stream() const { return m_stream->stream(); }\nstd::string Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }\nbool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; }\nbool Config::warnAboutMissingAssertions() const {\n    return !!(m_data.warnings & WarnAbout::NoAssertions);\n}\nbool Config::warnAboutNoTests() const { return !!(m_data.warnings & WarnAbout::NoTests); }\nShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; }\ndouble Config::minDuration() const { return m_data.minDuration; }\nRunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; }\nunsigned int Config::rngSeed() const { return m_data.rngSeed; }\nUseColour::YesOrNo Config::useColour() const { return m_data.useColour; }\nbool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; }\nint Config::abortAfter() const { return m_data.abortAfter; }\nbool Config::showInvisibles() const { return m_data.showInvisibles; }\nVerbosity Config::verbosity() const { return m_data.verbosity; }\n\nbool Config::benchmarkNoAnalysis() const { return m_data.benchmarkNoAnalysis; }\nint Config::benchmarkSamples() const { return m_data.benchmarkSamples; }\ndouble Config::benchmarkConfidenceInterval() const { return m_data.benchmarkConfidenceInterval; }\nunsigned int Config::benchmarkResamples() const { return m_data.benchmarkResamples; }\nstd::chrono::milliseconds Config::benchmarkWarmupTime() const {\n    return std::chrono::milliseconds(m_data.benchmarkWarmupTime);\n}\n\nIStream const *Config::openStream() { return Catch::makeStream(m_data.outputFilename); }\n\n}  // end namespace Catch\n// end catch_config.cpp\n// start catch_console_colour.cpp\n\n#if defined(__clang__)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n#endif\n\n// start catch_errno_guard.h\n\nnamespace Catch {\n\nclass ErrnoGuard {\npublic:\n    ErrnoGuard();\n    ~ErrnoGuard();\n\nprivate:\n    int m_oldErrno;\n};\n\n}  // namespace Catch\n\n// end catch_errno_guard.h\n// start catch_windows_h_proxy.h\n\n#if defined(CATCH_PLATFORM_WINDOWS)\n\n#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX)\n#define CATCH_DEFINED_NOMINMAX\n#define NOMINMAX\n#endif\n#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN)\n#define CATCH_DEFINED_WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n\n#ifdef __AFXDLL\n#include <AfxWin.h>\n#else\n#include <windows.h>\n#endif\n\n#ifdef CATCH_DEFINED_NOMINMAX\n#undef NOMINMAX\n#endif\n#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN\n#undef WIN32_LEAN_AND_MEAN\n#endif\n\n#endif  // defined(CATCH_PLATFORM_WINDOWS)\n\n// end catch_windows_h_proxy.h\n#include <sstream>\n\nnamespace Catch {\nnamespace {\n\nstruct IColourImpl {\n    virtual ~IColourImpl() = default;\n    virtual void use(Colour::Code _colourCode) = 0;\n};\n\nstruct NoColourImpl : IColourImpl {\n    void use(Colour::Code) override {}\n\n    static IColourImpl *instance() {\n        static NoColourImpl s_instance;\n        return &s_instance;\n    }\n};\n\n}  // namespace\n}  // namespace Catch\n\n#if !defined(CATCH_CONFIG_COLOUR_NONE) && !defined(CATCH_CONFIG_COLOUR_WINDOWS) && \\\n        !defined(CATCH_CONFIG_COLOUR_ANSI)\n#ifdef CATCH_PLATFORM_WINDOWS\n#define CATCH_CONFIG_COLOUR_WINDOWS\n#else\n#define CATCH_CONFIG_COLOUR_ANSI\n#endif\n#endif\n\n#if defined(CATCH_CONFIG_COLOUR_WINDOWS)  /////////////////////////////////////////\n\nnamespace Catch {\nnamespace {\n\nclass Win32ColourImpl : public IColourImpl {\npublic:\n    Win32ColourImpl() : stdoutHandle(GetStdHandle(STD_OUTPUT_HANDLE)) {\n        CONSOLE_SCREEN_BUFFER_INFO csbiInfo;\n        GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo);\n        originalForegroundAttributes =\n                csbiInfo.wAttributes &\n                ~(BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY);\n        originalBackgroundAttributes =\n                csbiInfo.wAttributes &\n                ~(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY);\n    }\n\n    void use(Colour::Code _colourCode) override {\n        switch (_colourCode) {\n        case Colour::None:\n            return setTextAttribute(originalForegroundAttributes);\n        case Colour::White:\n            return setTextAttribute(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE);\n        case Colour::Red:\n            return setTextAttribute(FOREGROUND_RED);\n        case Colour::Green:\n            return setTextAttribute(FOREGROUND_GREEN);\n        case Colour::Blue:\n            return setTextAttribute(FOREGROUND_BLUE);\n        case Colour::Cyan:\n            return setTextAttribute(FOREGROUND_BLUE | FOREGROUND_GREEN);\n        case Colour::Yellow:\n            return setTextAttribute(FOREGROUND_RED | FOREGROUND_GREEN);\n        case Colour::Grey:\n            return setTextAttribute(0);\n\n        case Colour::LightGrey:\n            return setTextAttribute(FOREGROUND_INTENSITY);\n        case Colour::BrightRed:\n            return setTextAttribute(FOREGROUND_INTENSITY | FOREGROUND_RED);\n        case Colour::BrightGreen:\n            return setTextAttribute(FOREGROUND_INTENSITY | FOREGROUND_GREEN);\n        case Colour::BrightWhite:\n            return setTextAttribute(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED |\n                                    FOREGROUND_BLUE);\n        case Colour::BrightYellow:\n            return setTextAttribute(FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN);\n\n        case Colour::Bright:\n            CATCH_INTERNAL_ERROR(\"not a colour\");\n\n        default:\n            CATCH_ERROR(\"Unknown colour requested\");\n        }\n    }\n\nprivate:\n    void setTextAttribute(WORD _textAttribute) {\n        SetConsoleTextAttribute(stdoutHandle, _textAttribute | originalBackgroundAttributes);\n    }\n    HANDLE stdoutHandle;\n    WORD originalForegroundAttributes;\n    WORD originalBackgroundAttributes;\n};\n\nIColourImpl *platformColourInstance() {\n    static Win32ColourImpl s_instance;\n\n    IConfigPtr config = getCurrentContext().getConfig();\n    UseColour::YesOrNo colourMode = config ? config->useColour() : UseColour::Auto;\n    if (colourMode == UseColour::Auto)\n        colourMode = UseColour::Yes;\n    return colourMode == UseColour::Yes ? &s_instance : NoColourImpl::instance();\n}\n\n}  // namespace\n}  // end namespace Catch\n\n#elif defined(CATCH_CONFIG_COLOUR_ANSI)  //////////////////////////////////////\n\n#include <unistd.h>\n\nnamespace Catch {\nnamespace {\n\n// use POSIX/ ANSI console terminal codes\n// Thanks to Adam Strzelecki for original contribution\n// (http://github.com/nanoant)\n// https://github.com/philsquared/Catch/pull/131\nclass PosixColourImpl : public IColourImpl {\npublic:\n    void use(Colour::Code _colourCode) override {\n        switch (_colourCode) {\n        case Colour::None:\n        case Colour::White:\n            return setColour(\"[0m\");\n        case Colour::Red:\n            return setColour(\"[0;31m\");\n        case Colour::Green:\n            return setColour(\"[0;32m\");\n        case Colour::Blue:\n            return setColour(\"[0;34m\");\n        case Colour::Cyan:\n            return setColour(\"[0;36m\");\n        case Colour::Yellow:\n            return setColour(\"[0;33m\");\n        case Colour::Grey:\n            return setColour(\"[1;30m\");\n\n        case Colour::LightGrey:\n            return setColour(\"[0;37m\");\n        case Colour::BrightRed:\n            return setColour(\"[1;31m\");\n        case Colour::BrightGreen:\n            return setColour(\"[1;32m\");\n        case Colour::BrightWhite:\n            return setColour(\"[1;37m\");\n        case Colour::BrightYellow:\n            return setColour(\"[1;33m\");\n\n        case Colour::Bright:\n            CATCH_INTERNAL_ERROR(\"not a colour\");\n        default:\n            CATCH_INTERNAL_ERROR(\"Unknown colour requested\");\n        }\n    }\n    static IColourImpl *instance() {\n        static PosixColourImpl s_instance;\n        return &s_instance;\n    }\n\nprivate:\n    void setColour(const char *_escapeCode) {\n        getCurrentContext().getConfig()->stream() << '\\033' << _escapeCode;\n    }\n};\n\nbool useColourOnPlatform() {\n    return\n#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE)\n            !isDebuggerActive() &&\n#endif\n#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__))\n            isatty(STDOUT_FILENO)\n#else\n            false\n#endif\n                    ;\n}\nIColourImpl *platformColourInstance() {\n    ErrnoGuard guard;\n    IConfigPtr config = getCurrentContext().getConfig();\n    UseColour::YesOrNo colourMode = config ? config->useColour() : UseColour::Auto;\n    if (colourMode == UseColour::Auto)\n        colourMode = useColourOnPlatform() ? UseColour::Yes : UseColour::No;\n    return colourMode == UseColour::Yes ? PosixColourImpl::instance() : NoColourImpl::instance();\n}\n\n}  // namespace\n}  // end namespace Catch\n\n#else  // not Windows or ANSI ///////////////////////////////////////////////\n\nnamespace Catch {\n\nstatic IColourImpl *platformColourInstance() { return NoColourImpl::instance(); }\n\n}  // end namespace Catch\n\n#endif  // Windows/ ANSI/ None\n\nnamespace Catch {\n\nColour::Colour(Code _colourCode) { use(_colourCode); }\nColour::Colour(Colour &&other) noexcept {\n    m_moved = other.m_moved;\n    other.m_moved = true;\n}\nColour &Colour::operator=(Colour &&other) noexcept {\n    m_moved = other.m_moved;\n    other.m_moved = true;\n    return *this;\n}\n\nColour::~Colour() {\n    if (!m_moved)\n        use(None);\n}\n\nvoid Colour::use(Code _colourCode) {\n    static IColourImpl *impl = platformColourInstance();\n    // Strictly speaking, this cannot possibly happen.\n    // However, under some conditions it does happen (see #1626),\n    // and this change is small enough that we can let practicality\n    // triumph over purity in this case.\n    if (impl != nullptr) {\n        impl->use(_colourCode);\n    }\n}\n\nstd::ostream &operator<<(std::ostream &os, Colour const &) { return os; }\n\n}  // end namespace Catch\n\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n\n// end catch_console_colour.cpp\n// start catch_context.cpp\n\nnamespace Catch {\n\nclass Context : public IMutableContext, NonCopyable {\npublic:  // IContext\n    IResultCapture *getResultCapture() override { return m_resultCapture; }\n    IRunner *getRunner() override { return m_runner; }\n\n    IConfigPtr const &getConfig() const override { return m_config; }\n\n    ~Context() override;\n\npublic:  // IMutableContext\n    void setResultCapture(IResultCapture *resultCapture) override {\n        m_resultCapture = resultCapture;\n    }\n    void setRunner(IRunner *runner) override { m_runner = runner; }\n    void setConfig(IConfigPtr const &config) override { m_config = config; }\n\n    friend IMutableContext &getCurrentMutableContext();\n\nprivate:\n    IConfigPtr m_config;\n    IRunner *m_runner = nullptr;\n    IResultCapture *m_resultCapture = nullptr;\n};\n\nIMutableContext *IMutableContext::currentContext = nullptr;\n\nvoid IMutableContext::createContext() { currentContext = new Context(); }\n\nvoid cleanUpContext() {\n    delete IMutableContext::currentContext;\n    IMutableContext::currentContext = nullptr;\n}\nIContext::~IContext() = default;\nIMutableContext::~IMutableContext() = default;\nContext::~Context() = default;\n\nSimplePcg32 &rng() {\n    static SimplePcg32 s_rng;\n    return s_rng;\n}\n\n}  // namespace Catch\n// end catch_context.cpp\n// start catch_debug_console.cpp\n\n// start catch_debug_console.h\n\n#include <string>\n\nnamespace Catch {\nvoid writeToDebugConsole(std::string const &text);\n}\n\n// end catch_debug_console.h\n#if defined(CATCH_CONFIG_ANDROID_LOGWRITE)\n#include <android/log.h>\n\nnamespace Catch {\nvoid writeToDebugConsole(std::string const &text) {\n    __android_log_write(ANDROID_LOG_DEBUG, \"Catch\", text.c_str());\n}\n}  // namespace Catch\n\n#elif defined(CATCH_PLATFORM_WINDOWS)\n\nnamespace Catch {\nvoid writeToDebugConsole(std::string const &text) { ::OutputDebugStringA(text.c_str()); }\n}  // namespace Catch\n\n#else\n\nnamespace Catch {\nvoid writeToDebugConsole(std::string const &text) {\n    // !TBD: Need a version for Mac/ XCode and other IDEs\n    Catch::cout() << text;\n}\n}  // namespace Catch\n\n#endif  // Platform\n// end catch_debug_console.cpp\n// start catch_debugger.cpp\n\n#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE)\n\n#include <sys/types.h>\n#include <unistd.h>\n\n#include <cassert>\n#include <cstddef>\n#include <ostream>\n\n#ifdef __apple_build_version__\n// These headers will only compile with AppleClang (XCode)\n// For other compilers (Clang, GCC, ... ) we need to exclude them\n#include <sys/sysctl.h>\n#endif\n\nnamespace Catch {\n#ifdef __apple_build_version__\n// The following function is taken directly from the following technical note:\n// https://developer.apple.com/library/archive/qa/qa1361/_index.html\n\n// Returns true if the current process is being debugged (either\n// running under the debugger or has a debugger attached post facto).\nbool isDebuggerActive() {\n    int mib[4];\n    struct kinfo_proc info;\n    std::size_t size;\n\n    // Initialize the flags so that, if sysctl fails for some bizarre\n    // reason, we get a predictable result.\n\n    info.kp_proc.p_flag = 0;\n\n    // Initialize mib, which tells sysctl the info we want, in this case\n    // we're looking for information about a specific process ID.\n\n    mib[0] = CTL_KERN;\n    mib[1] = KERN_PROC;\n    mib[2] = KERN_PROC_PID;\n    mib[3] = getpid();\n\n    // Call sysctl.\n\n    size = sizeof(info);\n    if (sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0) {\n        Catch::cerr() << \"\\n** Call to sysctl failed - unable to determine if \"\n                         \"debugger is active **\\n\"\n                      << std::endl;\n        return false;\n    }\n\n    // We're being debugged if the P_TRACED flag is set.\n\n    return ((info.kp_proc.p_flag & P_TRACED) != 0);\n}\n#else\nbool isDebuggerActive() {\n    // We need to find another way to determine this for non-appleclang compilers\n    // on macOS\n    return false;\n}\n#endif\n}  // namespace Catch\n\n#elif defined(CATCH_PLATFORM_LINUX)\n#include <fstream>\n#include <string>\n\nnamespace Catch {\n// The standard POSIX way of detecting a debugger is to attempt to\n// ptrace() the process, but this needs to be done from a child and not\n// this process itself to still allow attaching to this process later\n// if wanted, so is rather heavy. Under Linux we have the PID of the\n// \"debugger\" (which doesn't need to be gdb, of course, it could also\n// be strace, for example) in /proc/$PID/status, so just get it from\n// there instead.\nbool isDebuggerActive() {\n    // Libstdc++ has a bug, where std::ifstream sets errno to 0\n    // This way our users can properly assert over errno values\n    ErrnoGuard guard;\n    std::ifstream in(\"/proc/self/status\");\n    for (std::string line; std::getline(in, line);) {\n        static const int PREFIX_LEN = 11;\n        if (line.compare(0, PREFIX_LEN, \"TracerPid:\\t\") == 0) {\n            // We're traced if the PID is not 0 and no other PID starts\n            // with 0 digit, so it's enough to check for just a single\n            // character.\n            return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0';\n        }\n    }\n\n    return false;\n}\n}  // namespace Catch\n#elif defined(_MSC_VER)\nextern \"C\" __declspec(dllimport) int __stdcall IsDebuggerPresent();\nnamespace Catch {\nbool isDebuggerActive() { return IsDebuggerPresent() != 0; }\n}  // namespace Catch\n#elif defined(__MINGW32__)\nextern \"C\" __declspec(dllimport) int __stdcall IsDebuggerPresent();\nnamespace Catch {\nbool isDebuggerActive() { return IsDebuggerPresent() != 0; }\n}  // namespace Catch\n#else\nnamespace Catch {\nbool isDebuggerActive() { return false; }\n}  // namespace Catch\n#endif  // Platform\n// end catch_debugger.cpp\n// start catch_decomposer.cpp\n\nnamespace Catch {\n\nITransientExpression::~ITransientExpression() = default;\n\nvoid formatReconstructedExpression(std::ostream &os,\n                                   std::string const &lhs,\n                                   StringRef op,\n                                   std::string const &rhs) {\n    if (lhs.size() + rhs.size() < 40 && lhs.find('\\n') == std::string::npos &&\n        rhs.find('\\n') == std::string::npos)\n        os << lhs << \" \" << op << \" \" << rhs;\n    else\n        os << lhs << \"\\n\" << op << \"\\n\" << rhs;\n}\n}  // namespace Catch\n// end catch_decomposer.cpp\n// start catch_enforce.cpp\n\n#include <stdexcept>\n\nnamespace Catch {\n#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && \\\n        !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER)\n[[noreturn]] void throw_exception(std::exception const &e) {\n    Catch::cerr() << \"Catch will terminate because it needed to throw an exception.\\n\"\n                  << \"The message was: \" << e.what() << '\\n';\n    std::terminate();\n}\n#endif\n\n[[noreturn]] void throw_logic_error(std::string const &msg) {\n    throw_exception(std::logic_error(msg));\n}\n\n[[noreturn]] void throw_domain_error(std::string const &msg) {\n    throw_exception(std::domain_error(msg));\n}\n\n[[noreturn]] void throw_runtime_error(std::string const &msg) {\n    throw_exception(std::runtime_error(msg));\n}\n\n}  // namespace Catch\n// end catch_enforce.cpp\n// start catch_enum_values_registry.cpp\n// start catch_enum_values_registry.h\n\n#include <memory>\n#include <vector>\n\nnamespace Catch {\n\nnamespace Detail {\n\nstd::unique_ptr<EnumInfo> makeEnumInfo(StringRef enumName,\n                                       StringRef allValueNames,\n                                       std::vector<int> const &values);\n\nclass EnumValuesRegistry : public IMutableEnumValuesRegistry {\n    std::vector<std::unique_ptr<EnumInfo>> m_enumInfos;\n\n    EnumInfo const &registerEnum(StringRef enumName,\n                                 StringRef allEnums,\n                                 std::vector<int> const &values) override;\n};\n\nstd::vector<StringRef> parseEnums(StringRef enums);\n\n}  // namespace Detail\n\n}  // namespace Catch\n\n// end catch_enum_values_registry.h\n\n#include <cassert>\n#include <map>\n\nnamespace Catch {\n\nIMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() {}\n\nnamespace Detail {\n\nnamespace {\n// Extracts the actual name part of an enum instance\n// In other words, it returns the Blue part of Bikeshed::Colour::Blue\nStringRef extractInstanceName(StringRef enumInstance) {\n    // Find last occurrence of \":\"\n    size_t name_start = enumInstance.size();\n    while (name_start > 0 && enumInstance[name_start - 1] != ':') {\n        --name_start;\n    }\n    return enumInstance.substr(name_start, enumInstance.size() - name_start);\n}\n}  // namespace\n\nstd::vector<StringRef> parseEnums(StringRef enums) {\n    auto enumValues = splitStringRef(enums, ',');\n    std::vector<StringRef> parsed;\n    parsed.reserve(enumValues.size());\n    for (auto const &enumValue : enumValues) {\n        parsed.push_back(trim(extractInstanceName(enumValue)));\n    }\n    return parsed;\n}\n\nEnumInfo::~EnumInfo() {}\n\nStringRef EnumInfo::lookup(int value) const {\n    for (auto const &valueToName : m_values) {\n        if (valueToName.first == value)\n            return valueToName.second;\n    }\n    return \"{** unexpected enum value **}\"_sr;\n}\n\nstd::unique_ptr<EnumInfo> makeEnumInfo(StringRef enumName,\n                                       StringRef allValueNames,\n                                       std::vector<int> const &values) {\n    std::unique_ptr<EnumInfo> enumInfo(new EnumInfo);\n    enumInfo->m_name = enumName;\n    enumInfo->m_values.reserve(values.size());\n\n    const auto valueNames = Catch::Detail::parseEnums(allValueNames);\n    assert(valueNames.size() == values.size());\n    std::size_t i = 0;\n    for (auto value : values)\n        enumInfo->m_values.emplace_back(value, valueNames[i++]);\n\n    return enumInfo;\n}\n\nEnumInfo const &EnumValuesRegistry::registerEnum(StringRef enumName,\n                                                 StringRef allValueNames,\n                                                 std::vector<int> const &values) {\n    m_enumInfos.push_back(makeEnumInfo(enumName, allValueNames, values));\n    return *m_enumInfos.back();\n}\n\n}  // namespace Detail\n}  // namespace Catch\n\n// end catch_enum_values_registry.cpp\n// start catch_errno_guard.cpp\n\n#include <cerrno>\n\nnamespace Catch {\nErrnoGuard::ErrnoGuard() : m_oldErrno(errno) {}\nErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; }\n}  // namespace Catch\n// end catch_errno_guard.cpp\n// start catch_exception_translator_registry.cpp\n\n// start catch_exception_translator_registry.h\n\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\nclass ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry {\npublic:\n    ~ExceptionTranslatorRegistry();\n    virtual void registerTranslator(const IExceptionTranslator *translator);\n    std::string translateActiveException() const override;\n    std::string tryTranslators() const;\n\nprivate:\n    std::vector<std::unique_ptr<IExceptionTranslator const>> m_translators;\n};\n}  // namespace Catch\n\n// end catch_exception_translator_registry.h\n#ifdef __OBJC__\n#import \"Foundation/Foundation.h\"\n#endif\n\nnamespace Catch {\n\nExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() {}\n\nvoid ExceptionTranslatorRegistry::registerTranslator(const IExceptionTranslator *translator) {\n    m_translators.push_back(std::unique_ptr<const IExceptionTranslator>(translator));\n}\n\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\nstd::string ExceptionTranslatorRegistry::translateActiveException() const {\n    try {\n#ifdef __OBJC__\n        // In Objective-C try objective-c exceptions first\n        @try {\n            return tryTranslators();\n        } @catch (NSException *exception) {\n            return Catch::Detail::stringify([exception description]);\n        }\n#else\n        // Compiling a mixed mode project with MSVC means that CLR\n        // exceptions will be caught in (...) as well. However, these\n        // do not fill-in std::current_exception and thus lead to crash\n        // when attempting rethrow.\n        // /EHa switch also causes structured exceptions to be caught\n        // here, but they fill-in current_exception properly, so\n        // at worst the output should be a little weird, instead of\n        // causing a crash.\n        if (std::current_exception() == nullptr) {\n            return \"Non C++ exception. Possibly a CLR exception.\";\n        }\n        return tryTranslators();\n#endif\n    } catch (TestFailureException &) {\n        std::rethrow_exception(std::current_exception());\n    } catch (std::exception &ex) {\n        return ex.what();\n    } catch (std::string &msg) {\n        return msg;\n    } catch (const char *msg) {\n        return msg;\n    } catch (...) {\n        return \"Unknown exception\";\n    }\n}\n\nstd::string ExceptionTranslatorRegistry::tryTranslators() const {\n    if (m_translators.empty()) {\n        std::rethrow_exception(std::current_exception());\n    } else {\n        return m_translators[0]->translate(m_translators.begin() + 1, m_translators.end());\n    }\n}\n\n#else  // ^^ Exceptions are enabled // Exceptions are disabled vv\nstd::string ExceptionTranslatorRegistry::translateActiveException() const {\n    CATCH_INTERNAL_ERROR(\n            \"Attempted to translate active exception under \"\n            \"CATCH_CONFIG_DISABLE_EXCEPTIONS!\");\n}\n\nstd::string ExceptionTranslatorRegistry::tryTranslators() const {\n    CATCH_INTERNAL_ERROR(\n            \"Attempted to use exception translators under \"\n            \"CATCH_CONFIG_DISABLE_EXCEPTIONS!\");\n}\n#endif\n\n}  // namespace Catch\n// end catch_exception_translator_registry.cpp\n// start catch_fatal_condition.cpp\n\n#include <algorithm>\n\n#if !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_POSIX_SIGNALS)\n\nnamespace Catch {\n\n// If neither SEH nor signal handling is required, the handler impls\n// do not have to do anything, and can be empty.\nvoid FatalConditionHandler::engage_platform() {}\nvoid FatalConditionHandler::disengage_platform() {}\nFatalConditionHandler::FatalConditionHandler() = default;\nFatalConditionHandler::~FatalConditionHandler() = default;\n\n}  // end namespace Catch\n\n#endif  // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS\n\n#if defined(CATCH_CONFIG_WINDOWS_SEH) && defined(CATCH_CONFIG_POSIX_SIGNALS)\n#error \"Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time\"\n#endif  // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS\n\n#if defined(CATCH_CONFIG_WINDOWS_SEH) || defined(CATCH_CONFIG_POSIX_SIGNALS)\n\nnamespace {\n//! Signals fatal error message to the run context\nvoid reportFatal(char const *const message) {\n    Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition(message);\n}\n\n//! Minimal size Catch2 needs for its own fatal error handling.\n//! Picked anecdotally, so it might not be sufficient on all\n//! platforms, and for all configurations.\nconstexpr std::size_t minStackSizeForErrors = 32 * 1024;\n}  // end unnamed namespace\n\n#endif  // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS\n\n#if defined(CATCH_CONFIG_WINDOWS_SEH)\n\nnamespace Catch {\n\nstruct SignalDefs {\n    DWORD id;\n    const char *name;\n};\n\n// There is no 1-1 mapping between signals and windows exceptions.\n// Windows can easily distinguish between SO and SigSegV,\n// but SigInt, SigTerm, etc are handled differently.\nstatic SignalDefs signalDefs[] = {\n        {static_cast<DWORD>(EXCEPTION_ILLEGAL_INSTRUCTION), \"SIGILL - Illegal instruction signal\"},\n        {static_cast<DWORD>(EXCEPTION_STACK_OVERFLOW), \"SIGSEGV - Stack overflow\"},\n        {static_cast<DWORD>(EXCEPTION_ACCESS_VIOLATION), \"SIGSEGV - Segmentation violation signal\"},\n        {static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), \"Divide by zero error\"},\n};\n\nstatic LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) {\n    for (auto const &def : signalDefs) {\n        if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {\n            reportFatal(def.name);\n        }\n    }\n    // If its not an exception we care about, pass it along.\n    // This stops us from eating debugger breaks etc.\n    return EXCEPTION_CONTINUE_SEARCH;\n}\n\n// Since we do not support multiple instantiations, we put these\n// into global variables and rely on cleaning them up in outlined\n// constructors/destructors\nstatic PVOID exceptionHandlerHandle = nullptr;\n\n// For MSVC, we reserve part of the stack memory for handling\n// memory overflow structured exception.\nFatalConditionHandler::FatalConditionHandler() {\n    ULONG guaranteeSize = static_cast<ULONG>(minStackSizeForErrors);\n    if (!SetThreadStackGuarantee(&guaranteeSize)) {\n        // We do not want to fully error out, because needing\n        // the stack reserve should be rare enough anyway.\n        Catch::cerr() << \"Failed to reserve piece of stack.\"\n                      << \" Stack overflows will not be reported successfully.\";\n    }\n}\n\n// We do not attempt to unset the stack guarantee, because\n// Windows does not support lowering the stack size guarantee.\nFatalConditionHandler::~FatalConditionHandler() = default;\n\nvoid FatalConditionHandler::engage_platform() {\n    // Register as first handler in current chain\n    exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException);\n    if (!exceptionHandlerHandle) {\n        CATCH_RUNTIME_ERROR(\"Could not register vectored exception handler\");\n    }\n}\n\nvoid FatalConditionHandler::disengage_platform() {\n    if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) {\n        CATCH_RUNTIME_ERROR(\"Could not unregister vectored exception handler\");\n    }\n    exceptionHandlerHandle = nullptr;\n}\n\n}  // end namespace Catch\n\n#endif  // CATCH_CONFIG_WINDOWS_SEH\n\n#if defined(CATCH_CONFIG_POSIX_SIGNALS)\n\n#include <signal.h>\n\nnamespace Catch {\n\nstruct SignalDefs {\n    int id;\n    const char *name;\n};\n\nstatic SignalDefs signalDefs[] = {{SIGINT, \"SIGINT - Terminal interrupt signal\"},\n                                  {SIGILL, \"SIGILL - Illegal instruction signal\"},\n                                  {SIGFPE, \"SIGFPE - Floating point error signal\"},\n                                  {SIGSEGV, \"SIGSEGV - Segmentation violation signal\"},\n                                  {SIGTERM, \"SIGTERM - Termination request signal\"},\n                                  {SIGABRT, \"SIGABRT - Abort (abnormal termination) signal\"}};\n\n// Older GCCs trigger -Wmissing-field-initializers for T foo = {}\n// which is zero initialization, but not explicit. We want to avoid\n// that.\n#if defined(__GNUC__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wmissing-field-initializers\"\n#endif\n\nstatic char *altStackMem = nullptr;\nstatic std::size_t altStackSize = 0;\nstatic stack_t oldSigStack{};\nstatic struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{};\n\nstatic void restorePreviousSignalHandlers() {\n    // We set signal handlers back to the previous ones. Hopefully\n    // nobody overwrote them in the meantime, and doesn't expect\n    // their signal handlers to live past ours given that they\n    // installed them after ours..\n    for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {\n        sigaction(signalDefs[i].id, &oldSigActions[i], nullptr);\n    }\n    // Return the old stack\n    sigaltstack(&oldSigStack, nullptr);\n}\n\nstatic void handleSignal(int sig) {\n    char const *name = \"<unknown signal>\";\n    for (auto const &def : signalDefs) {\n        if (sig == def.id) {\n            name = def.name;\n            break;\n        }\n    }\n    // We need to restore previous signal handlers and let them do\n    // their thing, so that the users can have the debugger break\n    // when a signal is raised, and so on.\n    restorePreviousSignalHandlers();\n    reportFatal(name);\n    raise(sig);\n}\n\nFatalConditionHandler::FatalConditionHandler() {\n    assert(!altStackMem && \"Cannot initialize POSIX signal handler when one already exists\");\n    if (altStackSize == 0) {\n        altStackSize = std::max(static_cast<size_t>(SIGSTKSZ), minStackSizeForErrors);\n    }\n    altStackMem = new char[altStackSize]();\n}\n\nFatalConditionHandler::~FatalConditionHandler() {\n    delete[] altStackMem;\n    // We signal that another instance can be constructed by zeroing\n    // out the pointer.\n    altStackMem = nullptr;\n}\n\nvoid FatalConditionHandler::engage_platform() {\n    stack_t sigStack;\n    sigStack.ss_sp = altStackMem;\n    sigStack.ss_size = altStackSize;\n    sigStack.ss_flags = 0;\n    sigaltstack(&sigStack, &oldSigStack);\n    struct sigaction sa = {};\n\n    sa.sa_handler = handleSignal;\n    sa.sa_flags = SA_ONSTACK;\n    for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {\n        sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);\n    }\n}\n\n#if defined(__GNUC__)\n#pragma GCC diagnostic pop\n#endif\n\nvoid FatalConditionHandler::disengage_platform() { restorePreviousSignalHandlers(); }\n\n}  // end namespace Catch\n\n#endif  // CATCH_CONFIG_POSIX_SIGNALS\n// end catch_fatal_condition.cpp\n// start catch_generators.cpp\n\n#include <limits>\n#include <set>\n\nnamespace Catch {\n\nIGeneratorTracker::~IGeneratorTracker() {}\n\nconst char *GeneratorException::what() const noexcept { return m_msg; }\n\nnamespace Generators {\n\nGeneratorUntypedBase::~GeneratorUntypedBase() {}\n\nauto acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const &lineInfo)\n        -> IGeneratorTracker & {\n    return getResultCapture().acquireGeneratorTracker(generatorName, lineInfo);\n}\n\n}  // namespace Generators\n}  // namespace Catch\n// end catch_generators.cpp\n// start catch_interfaces_capture.cpp\n\nnamespace Catch {\nIResultCapture::~IResultCapture() = default;\n}\n// end catch_interfaces_capture.cpp\n// start catch_interfaces_config.cpp\n\nnamespace Catch {\nIConfig::~IConfig() = default;\n}\n// end catch_interfaces_config.cpp\n// start catch_interfaces_exception.cpp\n\nnamespace Catch {\nIExceptionTranslator::~IExceptionTranslator() = default;\nIExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default;\n}  // namespace Catch\n// end catch_interfaces_exception.cpp\n// start catch_interfaces_registry_hub.cpp\n\nnamespace Catch {\nIRegistryHub::~IRegistryHub() = default;\nIMutableRegistryHub::~IMutableRegistryHub() = default;\n}  // namespace Catch\n// end catch_interfaces_registry_hub.cpp\n// start catch_interfaces_reporter.cpp\n\n// start catch_reporter_listening.h\n\nnamespace Catch {\n\nclass ListeningReporter : public IStreamingReporter {\n    using Reporters = std::vector<IStreamingReporterPtr>;\n    Reporters m_listeners;\n    IStreamingReporterPtr m_reporter = nullptr;\n    ReporterPreferences m_preferences;\n\npublic:\n    ListeningReporter();\n\n    void addListener(IStreamingReporterPtr &&listener);\n    void addReporter(IStreamingReporterPtr &&reporter);\n\npublic:  // IStreamingReporter\n    ReporterPreferences getPreferences() const override;\n\n    void noMatchingTestCases(std::string const &spec) override;\n\n    void reportInvalidArguments(std::string const &arg) override;\n\n    static std::set<Verbosity> getSupportedVerbosities();\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n    void benchmarkPreparing(std::string const &name) override;\n    void benchmarkStarting(BenchmarkInfo const &benchmarkInfo) override;\n    void benchmarkEnded(BenchmarkStats<> const &benchmarkStats) override;\n    void benchmarkFailed(std::string const &) override;\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    void testRunStarting(TestRunInfo const &testRunInfo) override;\n    void testGroupStarting(GroupInfo const &groupInfo) override;\n    void testCaseStarting(TestCaseInfo const &testInfo) override;\n    void sectionStarting(SectionInfo const &sectionInfo) override;\n    void assertionStarting(AssertionInfo const &assertionInfo) override;\n\n    // The return value indicates if the messages buffer should be cleared:\n    bool assertionEnded(AssertionStats const &assertionStats) override;\n    void sectionEnded(SectionStats const &sectionStats) override;\n    void testCaseEnded(TestCaseStats const &testCaseStats) override;\n    void testGroupEnded(TestGroupStats const &testGroupStats) override;\n    void testRunEnded(TestRunStats const &testRunStats) override;\n\n    void skipTest(TestCaseInfo const &testInfo) override;\n    bool isMulti() const override;\n};\n\n}  // end namespace Catch\n\n// end catch_reporter_listening.h\nnamespace Catch {\n\nReporterConfig::ReporterConfig(IConfigPtr const &_fullConfig)\n        : m_stream(&_fullConfig->stream()), m_fullConfig(_fullConfig) {}\n\nReporterConfig::ReporterConfig(IConfigPtr const &_fullConfig, std::ostream &_stream)\n        : m_stream(&_stream), m_fullConfig(_fullConfig) {}\n\nstd::ostream &ReporterConfig::stream() const { return *m_stream; }\nIConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; }\n\nTestRunInfo::TestRunInfo(std::string const &_name) : name(_name) {}\n\nGroupInfo::GroupInfo(std::string const &_name, std::size_t _groupIndex, std::size_t _groupsCount)\n        : name(_name), groupIndex(_groupIndex), groupsCounts(_groupsCount) {}\n\nAssertionStats::AssertionStats(AssertionResult const &_assertionResult,\n                               std::vector<MessageInfo> const &_infoMessages,\n                               Totals const &_totals)\n        : assertionResult(_assertionResult), infoMessages(_infoMessages), totals(_totals) {\n    assertionResult.m_resultData.lazyExpression.m_transientExpression =\n            _assertionResult.m_resultData.lazyExpression.m_transientExpression;\n\n    if (assertionResult.hasMessage()) {\n        // Copy message into messages list.\n        // !TBD This should have been done earlier, somewhere\n        MessageBuilder builder(assertionResult.getTestMacroName(), assertionResult.getSourceInfo(),\n                               assertionResult.getResultType());\n        builder << assertionResult.getMessage();\n        builder.m_info.message = builder.m_stream.str();\n\n        infoMessages.push_back(builder.m_info);\n    }\n}\n\nAssertionStats::~AssertionStats() = default;\n\nSectionStats::SectionStats(SectionInfo const &_sectionInfo,\n                           Counts const &_assertions,\n                           double _durationInSeconds,\n                           bool _missingAssertions)\n        : sectionInfo(_sectionInfo),\n          assertions(_assertions),\n          durationInSeconds(_durationInSeconds),\n          missingAssertions(_missingAssertions) {}\n\nSectionStats::~SectionStats() = default;\n\nTestCaseStats::TestCaseStats(TestCaseInfo const &_testInfo,\n                             Totals const &_totals,\n                             std::string const &_stdOut,\n                             std::string const &_stdErr,\n                             bool _aborting)\n        : testInfo(_testInfo),\n          totals(_totals),\n          stdOut(_stdOut),\n          stdErr(_stdErr),\n          aborting(_aborting) {}\n\nTestCaseStats::~TestCaseStats() = default;\n\nTestGroupStats::TestGroupStats(GroupInfo const &_groupInfo, Totals const &_totals, bool _aborting)\n        : groupInfo(_groupInfo), totals(_totals), aborting(_aborting) {}\n\nTestGroupStats::TestGroupStats(GroupInfo const &_groupInfo)\n        : groupInfo(_groupInfo), aborting(false) {}\n\nTestGroupStats::~TestGroupStats() = default;\n\nTestRunStats::TestRunStats(TestRunInfo const &_runInfo, Totals const &_totals, bool _aborting)\n        : runInfo(_runInfo), totals(_totals), aborting(_aborting) {}\n\nTestRunStats::~TestRunStats() = default;\n\nvoid IStreamingReporter::fatalErrorEncountered(StringRef) {}\nbool IStreamingReporter::isMulti() const { return false; }\n\nIReporterFactory::~IReporterFactory() = default;\nIReporterRegistry::~IReporterRegistry() = default;\n\n}  // end namespace Catch\n// end catch_interfaces_reporter.cpp\n// start catch_interfaces_runner.cpp\n\nnamespace Catch {\nIRunner::~IRunner() = default;\n}\n// end catch_interfaces_runner.cpp\n// start catch_interfaces_testcase.cpp\n\nnamespace Catch {\nITestInvoker::~ITestInvoker() = default;\nITestCaseRegistry::~ITestCaseRegistry() = default;\n}  // namespace Catch\n// end catch_interfaces_testcase.cpp\n// start catch_leak_detector.cpp\n\n#ifdef CATCH_CONFIG_WINDOWS_CRTDBG\n#include <crtdbg.h>\n\nnamespace Catch {\n\nLeakDetector::LeakDetector() {\n    int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);\n    flag |= _CRTDBG_LEAK_CHECK_DF;\n    flag |= _CRTDBG_ALLOC_MEM_DF;\n    _CrtSetDbgFlag(flag);\n    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);\n    _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);\n    // Change this to leaking allocation's number to break there\n    _CrtSetBreakAlloc(-1);\n}\n}  // namespace Catch\n\n#else\n\nCatch::LeakDetector::LeakDetector() {}\n\n#endif\n\nCatch::LeakDetector::~LeakDetector() { Catch::cleanUp(); }\n// end catch_leak_detector.cpp\n// start catch_list.cpp\n\n// start catch_list.h\n\n#include <set>\n\nnamespace Catch {\n\nstd::size_t listTests(Config const &config);\n\nstd::size_t listTestsNamesOnly(Config const &config);\n\nstruct TagInfo {\n    void add(std::string const &spelling);\n    std::string all() const;\n\n    std::set<std::string> spellings;\n    std::size_t count = 0;\n};\n\nstd::size_t listTags(Config const &config);\n\nstd::size_t listReporters();\n\nOption<std::size_t> list(std::shared_ptr<Config> const &config);\n\n}  // end namespace Catch\n\n// end catch_list.h\n// start catch_text.h\n\nnamespace Catch {\nusing namespace clara::TextFlow;\n}\n\n// end catch_text.h\n#include <algorithm>\n#include <iomanip>\n#include <limits>\n\nnamespace Catch {\n\nstd::size_t listTests(Config const &config) {\n    TestSpec const &testSpec = config.testSpec();\n    if (config.hasTestFilters())\n        Catch::cout() << \"Matching test cases:\\n\";\n    else {\n        Catch::cout() << \"All available test cases:\\n\";\n    }\n\n    auto matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config);\n    for (auto const &testCaseInfo : matchedTestCases) {\n        Colour::Code colour = testCaseInfo.isHidden() ? Colour::SecondaryText : Colour::None;\n        Colour colourGuard(colour);\n\n        Catch::cout() << Column(testCaseInfo.name).initialIndent(2).indent(4) << \"\\n\";\n        if (config.verbosity() >= Verbosity::High) {\n            Catch::cout() << Column(Catch::Detail::stringify(testCaseInfo.lineInfo)).indent(4)\n                          << std::endl;\n            std::string description = testCaseInfo.description;\n            if (description.empty())\n                description = \"(NO DESCRIPTION)\";\n            Catch::cout() << Column(description).indent(4) << std::endl;\n        }\n        if (!testCaseInfo.tags.empty())\n            Catch::cout() << Column(testCaseInfo.tagsAsString()).indent(6) << \"\\n\";\n    }\n\n    if (!config.hasTestFilters())\n        Catch::cout() << pluralise(matchedTestCases.size(), \"test case\") << '\\n' << std::endl;\n    else\n        Catch::cout() << pluralise(matchedTestCases.size(), \"matching test case\") << '\\n'\n                      << std::endl;\n    return matchedTestCases.size();\n}\n\nstd::size_t listTestsNamesOnly(Config const &config) {\n    TestSpec const &testSpec = config.testSpec();\n    std::size_t matchedTests = 0;\n    std::vector<TestCase> matchedTestCases =\n            filterTests(getAllTestCasesSorted(config), testSpec, config);\n    for (auto const &testCaseInfo : matchedTestCases) {\n        matchedTests++;\n        if (startsWith(testCaseInfo.name, '#'))\n            Catch::cout() << '\"' << testCaseInfo.name << '\"';\n        else\n            Catch::cout() << testCaseInfo.name;\n        if (config.verbosity() >= Verbosity::High)\n            Catch::cout() << \"\\t@\" << testCaseInfo.lineInfo;\n        Catch::cout() << std::endl;\n    }\n    return matchedTests;\n}\n\nvoid TagInfo::add(std::string const &spelling) {\n    ++count;\n    spellings.insert(spelling);\n}\n\nstd::string TagInfo::all() const {\n    size_t size = 0;\n    for (auto const &spelling : spellings) {\n        // Add 2 for the brackes\n        size += spelling.size() + 2;\n    }\n\n    std::string out;\n    out.reserve(size);\n    for (auto const &spelling : spellings) {\n        out += '[';\n        out += spelling;\n        out += ']';\n    }\n    return out;\n}\n\nstd::size_t listTags(Config const &config) {\n    TestSpec const &testSpec = config.testSpec();\n    if (config.hasTestFilters())\n        Catch::cout() << \"Tags for matching test cases:\\n\";\n    else {\n        Catch::cout() << \"All available tags:\\n\";\n    }\n\n    std::map<std::string, TagInfo> tagCounts;\n\n    std::vector<TestCase> matchedTestCases =\n            filterTests(getAllTestCasesSorted(config), testSpec, config);\n    for (auto const &testCase : matchedTestCases) {\n        for (auto const &tagName : testCase.getTestCaseInfo().tags) {\n            std::string lcaseTagName = toLower(tagName);\n            auto countIt = tagCounts.find(lcaseTagName);\n            if (countIt == tagCounts.end())\n                countIt = tagCounts.insert(std::make_pair(lcaseTagName, TagInfo())).first;\n            countIt->second.add(tagName);\n        }\n    }\n\n    for (auto const &tagCount : tagCounts) {\n        ReusableStringStream rss;\n        rss << \"  \" << std::setw(2) << tagCount.second.count << \"  \";\n        auto str = rss.str();\n        auto wrapper = Column(tagCount.second.all())\n                               .initialIndent(0)\n                               .indent(str.size())\n                               .width(CATCH_CONFIG_CONSOLE_WIDTH - 10);\n        Catch::cout() << str << wrapper << '\\n';\n    }\n    Catch::cout() << pluralise(tagCounts.size(), \"tag\") << '\\n' << std::endl;\n    return tagCounts.size();\n}\n\nstd::size_t listReporters() {\n    Catch::cout() << \"Available reporters:\\n\";\n    IReporterRegistry::FactoryMap const &factories =\n            getRegistryHub().getReporterRegistry().getFactories();\n    std::size_t maxNameLen = 0;\n    for (auto const &factoryKvp : factories)\n        maxNameLen = (std::max)(maxNameLen, factoryKvp.first.size());\n\n    for (auto const &factoryKvp : factories) {\n        Catch::cout() << Column(factoryKvp.first + \":\").indent(2).width(5 + maxNameLen) +\n                                 Column(factoryKvp.second->getDescription())\n                                         .initialIndent(0)\n                                         .indent(2)\n                                         .width(CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8)\n                      << \"\\n\";\n    }\n    Catch::cout() << std::endl;\n    return factories.size();\n}\n\nOption<std::size_t> list(std::shared_ptr<Config> const &config) {\n    Option<std::size_t> listedCount;\n    getCurrentMutableContext().setConfig(config);\n    if (config->listTests())\n        listedCount = listedCount.valueOr(0) + listTests(*config);\n    if (config->listTestNamesOnly())\n        listedCount = listedCount.valueOr(0) + listTestsNamesOnly(*config);\n    if (config->listTags())\n        listedCount = listedCount.valueOr(0) + listTags(*config);\n    if (config->listReporters())\n        listedCount = listedCount.valueOr(0) + listReporters();\n    return listedCount;\n}\n\n}  // end namespace Catch\n// end catch_list.cpp\n// start catch_matchers.cpp\n\nnamespace Catch {\nnamespace Matchers {\nnamespace Impl {\n\nstd::string MatcherUntypedBase::toString() const {\n    if (m_cachedToString.empty())\n        m_cachedToString = describe();\n    return m_cachedToString;\n}\n\nMatcherUntypedBase::~MatcherUntypedBase() = default;\n\n}  // namespace Impl\n}  // namespace Matchers\n\nusing namespace Matchers;\nusing Matchers::Impl::MatcherBase;\n\n}  // namespace Catch\n// end catch_matchers.cpp\n// start catch_matchers_exception.cpp\n\nnamespace Catch {\nnamespace Matchers {\nnamespace Exception {\n\nbool ExceptionMessageMatcher::match(std::exception const &ex) const {\n    return ex.what() == m_message;\n}\n\nstd::string ExceptionMessageMatcher::describe() const {\n    return \"exception message matches \\\"\" + m_message + \"\\\"\";\n}\n\n}  // namespace Exception\nException::ExceptionMessageMatcher Message(std::string const &message) {\n    return Exception::ExceptionMessageMatcher(message);\n}\n\n// namespace Exception\n}  // namespace Matchers\n}  // namespace Catch\n// end catch_matchers_exception.cpp\n// start catch_matchers_floating.cpp\n\n// start catch_polyfills.hpp\n\nnamespace Catch {\nbool isnan(float f);\nbool isnan(double d);\n}  // namespace Catch\n\n// end catch_polyfills.hpp\n// start catch_to_string.hpp\n\n#include <string>\n\nnamespace Catch {\ntemplate <typename T>\nstd::string to_string(T const &t) {\n#if defined(CATCH_CONFIG_CPP11_TO_STRING)\n    return std::to_string(t);\n#else\n    ReusableStringStream rss;\n    rss << t;\n    return rss.str();\n#endif\n}\n}  // end namespace Catch\n\n// end catch_to_string.hpp\n#include <algorithm>\n#include <cmath>\n#include <cstdint>\n#include <cstdlib>\n#include <cstring>\n#include <iomanip>\n#include <limits>\n#include <sstream>\n#include <type_traits>\n\nnamespace Catch {\nnamespace {\n\nint32_t convert(float f) {\n    static_assert(sizeof(float) == sizeof(int32_t), \"Important ULP matcher assumption violated\");\n    int32_t i;\n    std::memcpy(&i, &f, sizeof(f));\n    return i;\n}\n\nint64_t convert(double d) {\n    static_assert(sizeof(double) == sizeof(int64_t), \"Important ULP matcher assumption violated\");\n    int64_t i;\n    std::memcpy(&i, &d, sizeof(d));\n    return i;\n}\n\ntemplate <typename FP>\nbool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) {\n    // Comparison with NaN should always be false.\n    // This way we can rule it out before getting into the ugly details\n    if (Catch::isnan(lhs) || Catch::isnan(rhs)) {\n        return false;\n    }\n\n    auto lc = convert(lhs);\n    auto rc = convert(rhs);\n\n    if ((lc < 0) != (rc < 0)) {\n        // Potentially we can have +0 and -0\n        return lhs == rhs;\n    }\n\n    // static cast as a workaround for IBM XLC\n    auto ulpDiff = std::abs(static_cast<FP>(lc - rc));\n    return static_cast<uint64_t>(ulpDiff) <= maxUlpDiff;\n}\n\n#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)\n\nfloat nextafter(float x, float y) { return ::nextafterf(x, y); }\n\ndouble nextafter(double x, double y) { return ::nextafter(x, y); }\n\n#endif  // ^^^ CATCH_CONFIG_GLOBAL_NEXTAFTER ^^^\n\ntemplate <typename FP>\nFP step(FP start, FP direction, uint64_t steps) {\n    for (uint64_t i = 0; i < steps; ++i) {\n#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)\n        start = Catch::nextafter(start, direction);\n#else\n        start = std::nextafter(start, direction);\n#endif\n    }\n    return start;\n}\n\n// Performs equivalent check of std::fabs(lhs - rhs) <= margin\n// But without the subtraction to allow for INFINITY in comparison\nbool marginComparison(double lhs, double rhs, double margin) {\n    return (lhs + margin >= rhs) && (rhs + margin >= lhs);\n}\n\ntemplate <typename FloatingPoint>\nvoid write(std::ostream &out, FloatingPoint num) {\n    out << std::scientific\n        << std::setprecision(std::numeric_limits<FloatingPoint>::max_digits10 - 1) << num;\n}\n\n}  // end anonymous namespace\n\nnamespace Matchers {\nnamespace Floating {\n\nenum class FloatingPointKind : uint8_t { Float, Double };\n\nWithinAbsMatcher::WithinAbsMatcher(double target, double margin)\n        : m_target{target}, m_margin{margin} {\n    CATCH_ENFORCE(margin >= 0,\n                  \"Invalid margin: \" << margin << '.' << \" Margin has to be non-negative.\");\n}\n\n// Performs equivalent check of std::fabs(lhs - rhs) <= margin\n// But without the subtraction to allow for INFINITY in comparison\nbool WithinAbsMatcher::match(double const &matchee) const {\n    return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee);\n}\n\nstd::string WithinAbsMatcher::describe() const {\n    return \"is within \" + ::Catch::Detail::stringify(m_margin) + \" of \" +\n           ::Catch::Detail::stringify(m_target);\n}\n\nWithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType)\n        : m_target{target}, m_ulps{ulps}, m_type{baseType} {\n    CATCH_ENFORCE(\n            m_type == FloatingPointKind::Double || m_ulps < (std::numeric_limits<uint32_t>::max)(),\n            \"Provided ULP is impossibly large for a float comparison.\");\n}\n\n#if defined(__clang__)\n#pragma clang diagnostic push\n// Clang <3.5 reports on the default branch in the switch below\n#pragma clang diagnostic ignored \"-Wunreachable-code\"\n#endif\n\nbool WithinUlpsMatcher::match(double const &matchee) const {\n    switch (m_type) {\n    case FloatingPointKind::Float:\n        return almostEqualUlps<float>(static_cast<float>(matchee), static_cast<float>(m_target),\n                                      m_ulps);\n    case FloatingPointKind::Double:\n        return almostEqualUlps<double>(matchee, m_target, m_ulps);\n    default:\n        CATCH_INTERNAL_ERROR(\"Unknown FloatingPointKind value\");\n    }\n}\n\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n\nstd::string WithinUlpsMatcher::describe() const {\n    std::stringstream ret;\n\n    ret << \"is within \" << m_ulps << \" ULPs of \";\n\n    if (m_type == FloatingPointKind::Float) {\n        write(ret, static_cast<float>(m_target));\n        ret << 'f';\n    } else {\n        write(ret, m_target);\n    }\n\n    ret << \" ([\";\n    if (m_type == FloatingPointKind::Double) {\n        write(ret, step(m_target, static_cast<double>(-INFINITY), m_ulps));\n        ret << \", \";\n        write(ret, step(m_target, static_cast<double>(INFINITY), m_ulps));\n    } else {\n        // We have to cast INFINITY to float because of MinGW, see #1782\n        write(ret, step(static_cast<float>(m_target), static_cast<float>(-INFINITY), m_ulps));\n        ret << \", \";\n        write(ret, step(static_cast<float>(m_target), static_cast<float>(INFINITY), m_ulps));\n    }\n    ret << \"])\";\n\n    return ret.str();\n}\n\nWithinRelMatcher::WithinRelMatcher(double target, double epsilon)\n        : m_target(target), m_epsilon(epsilon) {\n    CATCH_ENFORCE(m_epsilon >= 0., \"Relative comparison with epsilon <  0 does not make sense.\");\n    CATCH_ENFORCE(m_epsilon < 1., \"Relative comparison with epsilon >= 1 does not make sense.\");\n}\n\nbool WithinRelMatcher::match(double const &matchee) const {\n    const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target));\n    return marginComparison(matchee, m_target, std::isinf(relMargin) ? 0 : relMargin);\n}\n\nstd::string WithinRelMatcher::describe() const {\n    Catch::ReusableStringStream sstr;\n    sstr << \"and \" << m_target << \" are within \" << m_epsilon * 100. << \"% of each other\";\n    return sstr.str();\n}\n\n}  // namespace Floating\n\nFloating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) {\n    return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double);\n}\n\nFloating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) {\n    return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float);\n}\n\nFloating::WithinAbsMatcher WithinAbs(double target, double margin) {\n    return Floating::WithinAbsMatcher(target, margin);\n}\n\nFloating::WithinRelMatcher WithinRel(double target, double eps) {\n    return Floating::WithinRelMatcher(target, eps);\n}\n\nFloating::WithinRelMatcher WithinRel(double target) {\n    return Floating::WithinRelMatcher(target, std::numeric_limits<double>::epsilon() * 100);\n}\n\nFloating::WithinRelMatcher WithinRel(float target, float eps) {\n    return Floating::WithinRelMatcher(target, eps);\n}\n\nFloating::WithinRelMatcher WithinRel(float target) {\n    return Floating::WithinRelMatcher(target, std::numeric_limits<float>::epsilon() * 100);\n}\n\n}  // namespace Matchers\n}  // namespace Catch\n// end catch_matchers_floating.cpp\n// start catch_matchers_generic.cpp\n\nstd::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string &desc) {\n    if (desc.empty()) {\n        return \"matches undescribed predicate\";\n    } else {\n        return \"matches predicate: \\\"\" + desc + '\"';\n    }\n}\n// end catch_matchers_generic.cpp\n// start catch_matchers_string.cpp\n\n#include <regex>\n\nnamespace Catch {\nnamespace Matchers {\n\nnamespace StdString {\n\nCasedString::CasedString(std::string const &str, CaseSensitive::Choice caseSensitivity)\n        : m_caseSensitivity(caseSensitivity), m_str(adjustString(str)) {}\nstd::string CasedString::adjustString(std::string const &str) const {\n    return m_caseSensitivity == CaseSensitive::No ? toLower(str) : str;\n}\nstd::string CasedString::caseSensitivitySuffix() const {\n    return m_caseSensitivity == CaseSensitive::No ? \" (case insensitive)\" : std::string();\n}\n\nStringMatcherBase::StringMatcherBase(std::string const &operation, CasedString const &comparator)\n        : m_comparator(comparator), m_operation(operation) {}\n\nstd::string StringMatcherBase::describe() const {\n    std::string description;\n    description.reserve(5 + m_operation.size() + m_comparator.m_str.size() +\n                        m_comparator.caseSensitivitySuffix().size());\n    description += m_operation;\n    description += \": \\\"\";\n    description += m_comparator.m_str;\n    description += \"\\\"\";\n    description += m_comparator.caseSensitivitySuffix();\n    return description;\n}\n\nEqualsMatcher::EqualsMatcher(CasedString const &comparator)\n        : StringMatcherBase(\"equals\", comparator) {}\n\nbool EqualsMatcher::match(std::string const &source) const {\n    return m_comparator.adjustString(source) == m_comparator.m_str;\n}\n\nContainsMatcher::ContainsMatcher(CasedString const &comparator)\n        : StringMatcherBase(\"contains\", comparator) {}\n\nbool ContainsMatcher::match(std::string const &source) const {\n    return contains(m_comparator.adjustString(source), m_comparator.m_str);\n}\n\nStartsWithMatcher::StartsWithMatcher(CasedString const &comparator)\n        : StringMatcherBase(\"starts with\", comparator) {}\n\nbool StartsWithMatcher::match(std::string const &source) const {\n    return startsWith(m_comparator.adjustString(source), m_comparator.m_str);\n}\n\nEndsWithMatcher::EndsWithMatcher(CasedString const &comparator)\n        : StringMatcherBase(\"ends with\", comparator) {}\n\nbool EndsWithMatcher::match(std::string const &source) const {\n    return endsWith(m_comparator.adjustString(source), m_comparator.m_str);\n}\n\nRegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity)\n        : m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {}\n\nbool RegexMatcher::match(std::string const &matchee) const {\n    auto flags = std::regex::ECMAScript;  // ECMAScript is the default syntax option anyway\n    if (m_caseSensitivity == CaseSensitive::Choice::No) {\n        flags |= std::regex::icase;\n    }\n    auto reg = std::regex(m_regex, flags);\n    return std::regex_match(matchee, reg);\n}\n\nstd::string RegexMatcher::describe() const {\n    return \"matches \" + ::Catch::Detail::stringify(m_regex) +\n           ((m_caseSensitivity == CaseSensitive::Choice::Yes) ? \" case sensitively\"\n                                                              : \" case insensitively\");\n}\n\n}  // namespace StdString\n\nStdString::EqualsMatcher Equals(std::string const &str, CaseSensitive::Choice caseSensitivity) {\n    return StdString::EqualsMatcher(StdString::CasedString(str, caseSensitivity));\n}\nStdString::ContainsMatcher Contains(std::string const &str, CaseSensitive::Choice caseSensitivity) {\n    return StdString::ContainsMatcher(StdString::CasedString(str, caseSensitivity));\n}\nStdString::EndsWithMatcher EndsWith(std::string const &str, CaseSensitive::Choice caseSensitivity) {\n    return StdString::EndsWithMatcher(StdString::CasedString(str, caseSensitivity));\n}\nStdString::StartsWithMatcher StartsWith(std::string const &str,\n                                        CaseSensitive::Choice caseSensitivity) {\n    return StdString::StartsWithMatcher(StdString::CasedString(str, caseSensitivity));\n}\n\nStdString::RegexMatcher Matches(std::string const &regex, CaseSensitive::Choice caseSensitivity) {\n    return StdString::RegexMatcher(regex, caseSensitivity);\n}\n\n}  // namespace Matchers\n}  // namespace Catch\n// end catch_matchers_string.cpp\n// start catch_message.cpp\n\n// start catch_uncaught_exceptions.h\n\nnamespace Catch {\nbool uncaught_exceptions();\n}  // end namespace Catch\n\n// end catch_uncaught_exceptions.h\n#include <cassert>\n#include <stack>\n\nnamespace Catch {\n\nMessageInfo::MessageInfo(StringRef const &_macroName,\n                         SourceLineInfo const &_lineInfo,\n                         ResultWas::OfType _type)\n        : macroName(_macroName), lineInfo(_lineInfo), type(_type), sequence(++globalCount) {}\n\nbool MessageInfo::operator==(MessageInfo const &other) const { return sequence == other.sequence; }\n\nbool MessageInfo::operator<(MessageInfo const &other) const { return sequence < other.sequence; }\n\n// This may need protecting if threading support is added\nunsigned int MessageInfo::globalCount = 0;\n\n////////////////////////////////////////////////////////////////////////////\n\nCatch::MessageBuilder::MessageBuilder(StringRef const &macroName,\n                                      SourceLineInfo const &lineInfo,\n                                      ResultWas::OfType type)\n        : m_info(macroName, lineInfo, type) {}\n\n////////////////////////////////////////////////////////////////////////////\n\nScopedMessage::ScopedMessage(MessageBuilder const &builder) : m_info(builder.m_info), m_moved() {\n    m_info.message = builder.m_stream.str();\n    getResultCapture().pushScopedMessage(m_info);\n}\n\nScopedMessage::ScopedMessage(ScopedMessage &&old) : m_info(old.m_info), m_moved() {\n    old.m_moved = true;\n}\n\nScopedMessage::~ScopedMessage() {\n    if (!uncaught_exceptions() && !m_moved) {\n        getResultCapture().popScopedMessage(m_info);\n    }\n}\n\nCapturer::Capturer(StringRef macroName,\n                   SourceLineInfo const &lineInfo,\n                   ResultWas::OfType resultType,\n                   StringRef names) {\n    auto trimmed = [&](size_t start, size_t end) {\n        while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) {\n            ++start;\n        }\n        while (names[end] == ',' || isspace(static_cast<unsigned char>(names[end]))) {\n            --end;\n        }\n        return names.substr(start, end - start + 1);\n    };\n    auto skipq = [&](size_t start, char quote) {\n        for (auto i = start + 1; i < names.size(); ++i) {\n            if (names[i] == quote)\n                return i;\n            if (names[i] == '\\\\')\n                ++i;\n        }\n        CATCH_INTERNAL_ERROR(\"CAPTURE parsing encountered unmatched quote\");\n    };\n\n    size_t start = 0;\n    std::stack<char> openings;\n    for (size_t pos = 0; pos < names.size(); ++pos) {\n        char c = names[pos];\n        switch (c) {\n        case '[':\n        case '{':\n        case '(':\n            // It is basically impossible to disambiguate between\n            // comparison and start of template args in this context\n            //            case '<':\n            openings.push(c);\n            break;\n        case ']':\n        case '}':\n        case ')':\n            //           case '>':\n            openings.pop();\n            break;\n        case '\"':\n        case '\\'':\n            pos = skipq(pos, c);\n            break;\n        case ',':\n            if (start != pos && openings.empty()) {\n                m_messages.emplace_back(macroName, lineInfo, resultType);\n                m_messages.back().message = static_cast<std::string>(trimmed(start, pos));\n                m_messages.back().message += \" := \";\n                start = pos;\n            }\n        }\n    }\n    assert(openings.empty() && \"Mismatched openings\");\n    m_messages.emplace_back(macroName, lineInfo, resultType);\n    m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1));\n    m_messages.back().message += \" := \";\n}\nCapturer::~Capturer() {\n    if (!uncaught_exceptions()) {\n        assert(m_captured == m_messages.size());\n        for (size_t i = 0; i < m_captured; ++i)\n            m_resultCapture.popScopedMessage(m_messages[i]);\n    }\n}\n\nvoid Capturer::captureValue(size_t index, std::string const &value) {\n    assert(index < m_messages.size());\n    m_messages[index].message += value;\n    m_resultCapture.pushScopedMessage(m_messages[index]);\n    m_captured++;\n}\n\n}  // end namespace Catch\n// end catch_message.cpp\n// start catch_output_redirect.cpp\n\n// start catch_output_redirect.h\n#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H\n#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H\n\n#include <cstdio>\n#include <iosfwd>\n#include <string>\n\nnamespace Catch {\n\nclass RedirectedStream {\n    std::ostream &m_originalStream;\n    std::ostream &m_redirectionStream;\n    std::streambuf *m_prevBuf;\n\npublic:\n    RedirectedStream(std::ostream &originalStream, std::ostream &redirectionStream);\n    ~RedirectedStream();\n};\n\nclass RedirectedStdOut {\n    ReusableStringStream m_rss;\n    RedirectedStream m_cout;\n\npublic:\n    RedirectedStdOut();\n    auto str() const -> std::string;\n};\n\n// StdErr has two constituent streams in C++, std::cerr and std::clog\n// This means that we need to redirect 2 streams into 1 to keep proper\n// order of writes\nclass RedirectedStdErr {\n    ReusableStringStream m_rss;\n    RedirectedStream m_cerr;\n    RedirectedStream m_clog;\n\npublic:\n    RedirectedStdErr();\n    auto str() const -> std::string;\n};\n\nclass RedirectedStreams {\npublic:\n    RedirectedStreams(RedirectedStreams const &) = delete;\n    RedirectedStreams &operator=(RedirectedStreams const &) = delete;\n    RedirectedStreams(RedirectedStreams &&) = delete;\n    RedirectedStreams &operator=(RedirectedStreams &&) = delete;\n\n    RedirectedStreams(std::string &redirectedCout, std::string &redirectedCerr);\n    ~RedirectedStreams();\n\nprivate:\n    std::string &m_redirectedCout;\n    std::string &m_redirectedCerr;\n    RedirectedStdOut m_redirectedStdOut;\n    RedirectedStdErr m_redirectedStdErr;\n};\n\n#if defined(CATCH_CONFIG_NEW_CAPTURE)\n\n// Windows's implementation of std::tmpfile is terrible (it tries\n// to create a file inside system folder, thus requiring elevated\n// privileges for the binary), so we have to use tmpnam(_s) and\n// create the file ourselves there.\nclass TempFile {\npublic:\n    TempFile(TempFile const &) = delete;\n    TempFile &operator=(TempFile const &) = delete;\n    TempFile(TempFile &&) = delete;\n    TempFile &operator=(TempFile &&) = delete;\n\n    TempFile();\n    ~TempFile();\n\n    std::FILE *getFile();\n    std::string getContents();\n\nprivate:\n    std::FILE *m_file = nullptr;\n#if defined(_MSC_VER)\n    char m_buffer[L_tmpnam] = {0};\n#endif\n};\n\nclass OutputRedirect {\npublic:\n    OutputRedirect(OutputRedirect const &) = delete;\n    OutputRedirect &operator=(OutputRedirect const &) = delete;\n    OutputRedirect(OutputRedirect &&) = delete;\n    OutputRedirect &operator=(OutputRedirect &&) = delete;\n\n    OutputRedirect(std::string &stdout_dest, std::string &stderr_dest);\n    ~OutputRedirect();\n\nprivate:\n    int m_originalStdout = -1;\n    int m_originalStderr = -1;\n    TempFile m_stdoutFile;\n    TempFile m_stderrFile;\n    std::string &m_stdoutDest;\n    std::string &m_stderrDest;\n};\n\n#endif\n\n}  // end namespace Catch\n\n#endif  // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H\n// end catch_output_redirect.h\n#include <cstdio>\n#include <cstring>\n#include <fstream>\n#include <sstream>\n#include <stdexcept>\n\n#if defined(CATCH_CONFIG_NEW_CAPTURE)\n#if defined(_MSC_VER)\n#include <io.h>  //_dup and _dup2\n#define dup _dup\n#define dup2 _dup2\n#define fileno _fileno\n#else\n#include <unistd.h>  // dup and dup2\n#endif\n#endif\n\nnamespace Catch {\n\nRedirectedStream::RedirectedStream(std::ostream &originalStream, std::ostream &redirectionStream)\n        : m_originalStream(originalStream),\n          m_redirectionStream(redirectionStream),\n          m_prevBuf(m_originalStream.rdbuf()) {\n    m_originalStream.rdbuf(m_redirectionStream.rdbuf());\n}\n\nRedirectedStream::~RedirectedStream() { m_originalStream.rdbuf(m_prevBuf); }\n\nRedirectedStdOut::RedirectedStdOut() : m_cout(Catch::cout(), m_rss.get()) {}\nauto RedirectedStdOut::str() const -> std::string { return m_rss.str(); }\n\nRedirectedStdErr::RedirectedStdErr()\n        : m_cerr(Catch::cerr(), m_rss.get()), m_clog(Catch::clog(), m_rss.get()) {}\nauto RedirectedStdErr::str() const -> std::string { return m_rss.str(); }\n\nRedirectedStreams::RedirectedStreams(std::string &redirectedCout, std::string &redirectedCerr)\n        : m_redirectedCout(redirectedCout), m_redirectedCerr(redirectedCerr) {}\n\nRedirectedStreams::~RedirectedStreams() {\n    m_redirectedCout += m_redirectedStdOut.str();\n    m_redirectedCerr += m_redirectedStdErr.str();\n}\n\n#if defined(CATCH_CONFIG_NEW_CAPTURE)\n\n#if defined(_MSC_VER)\nTempFile::TempFile() {\n    if (tmpnam_s(m_buffer)) {\n        CATCH_RUNTIME_ERROR(\"Could not get a temp filename\");\n    }\n    if (fopen_s(&m_file, m_buffer, \"w+\")) {\n        char buffer[100];\n        if (strerror_s(buffer, errno)) {\n            CATCH_RUNTIME_ERROR(\"Could not translate errno to a string\");\n        }\n        CATCH_RUNTIME_ERROR(\"Could not open the temp file: '\" << m_buffer\n                                                              << \"' because: \" << buffer);\n    }\n}\n#else\nTempFile::TempFile() {\n    m_file = std::tmpfile();\n    if (!m_file) {\n        CATCH_RUNTIME_ERROR(\"Could not create a temp file.\");\n    }\n}\n\n#endif\n\nTempFile::~TempFile() {\n    // TBD: What to do about errors here?\n    std::fclose(m_file);\n    // We manually create the file on Windows only, on Linux\n    // it will be autodeleted\n#if defined(_MSC_VER)\n    std::remove(m_buffer);\n#endif\n}\n\nFILE *TempFile::getFile() { return m_file; }\n\nstd::string TempFile::getContents() {\n    std::stringstream sstr;\n    char buffer[100] = {};\n    std::rewind(m_file);\n    while (std::fgets(buffer, sizeof(buffer), m_file)) {\n        sstr << buffer;\n    }\n    return sstr.str();\n}\n\nOutputRedirect::OutputRedirect(std::string &stdout_dest, std::string &stderr_dest)\n        : m_originalStdout(dup(1)),\n          m_originalStderr(dup(2)),\n          m_stdoutDest(stdout_dest),\n          m_stderrDest(stderr_dest) {\n    dup2(fileno(m_stdoutFile.getFile()), 1);\n    dup2(fileno(m_stderrFile.getFile()), 2);\n}\n\nOutputRedirect::~OutputRedirect() {\n    Catch::cout() << std::flush;\n    fflush(stdout);\n    // Since we support overriding these streams, we flush cerr\n    // even though std::cerr is unbuffered\n    Catch::cerr() << std::flush;\n    Catch::clog() << std::flush;\n    fflush(stderr);\n\n    dup2(m_originalStdout, 1);\n    dup2(m_originalStderr, 2);\n\n    m_stdoutDest += m_stdoutFile.getContents();\n    m_stderrDest += m_stderrFile.getContents();\n}\n\n#endif  // CATCH_CONFIG_NEW_CAPTURE\n\n}  // namespace Catch\n\n#if defined(CATCH_CONFIG_NEW_CAPTURE)\n#if defined(_MSC_VER)\n#undef dup\n#undef dup2\n#undef fileno\n#endif\n#endif\n// end catch_output_redirect.cpp\n// start catch_polyfills.cpp\n\n#include <cmath>\n\nnamespace Catch {\n\n#if !defined(CATCH_CONFIG_POLYFILL_ISNAN)\nbool isnan(float f) { return std::isnan(f); }\nbool isnan(double d) { return std::isnan(d); }\n#else\n// For now we only use this for embarcadero\nbool isnan(float f) { return std::_isnan(f); }\nbool isnan(double d) { return std::_isnan(d); }\n#endif\n\n}  // end namespace Catch\n// end catch_polyfills.cpp\n// start catch_random_number_generator.cpp\n\nnamespace Catch {\n\nnamespace {\n\n#if defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable : 4146)  // we negate uint32 during the rotate\n#endif\n// Safe rotr implementation thanks to John Regehr\nuint32_t rotate_right(uint32_t val, uint32_t count) {\n    const uint32_t mask = 31;\n    count &= mask;\n    return (val >> count) | (val << (-count & mask));\n}\n\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n\n}  // namespace\n\nSimplePcg32::SimplePcg32(result_type seed_) { seed(seed_); }\n\nvoid SimplePcg32::seed(result_type seed_) {\n    m_state = 0;\n    (*this)();\n    m_state += seed_;\n    (*this)();\n}\n\nvoid SimplePcg32::discard(uint64_t skip) {\n    // We could implement this to run in O(log n) steps, but this\n    // should suffice for our use case.\n    for (uint64_t s = 0; s < skip; ++s) {\n        static_cast<void>((*this)());\n    }\n}\n\nSimplePcg32::result_type SimplePcg32::operator()() {\n    // prepare the output value\n    const uint32_t xorshifted = static_cast<uint32_t>(((m_state >> 18u) ^ m_state) >> 27u);\n    const auto output = rotate_right(xorshifted, m_state >> 59u);\n\n    // advance state\n    m_state = m_state * 6364136223846793005ULL + s_inc;\n\n    return output;\n}\n\nbool operator==(SimplePcg32 const &lhs, SimplePcg32 const &rhs) {\n    return lhs.m_state == rhs.m_state;\n}\n\nbool operator!=(SimplePcg32 const &lhs, SimplePcg32 const &rhs) {\n    return lhs.m_state != rhs.m_state;\n}\n}  // namespace Catch\n// end catch_random_number_generator.cpp\n// start catch_registry_hub.cpp\n\n// start catch_test_case_registry_impl.h\n\n#include <algorithm>\n#include <ios>\n#include <set>\n#include <vector>\n\nnamespace Catch {\n\nclass TestCase;\nstruct IConfig;\n\nstd::vector<TestCase> sortTests(IConfig const &config,\n                                std::vector<TestCase> const &unsortedTestCases);\n\nbool isThrowSafe(TestCase const &testCase, IConfig const &config);\nbool matchTest(TestCase const &testCase, TestSpec const &testSpec, IConfig const &config);\n\nvoid enforceNoDuplicateTestCases(std::vector<TestCase> const &functions);\n\nstd::vector<TestCase> filterTests(std::vector<TestCase> const &testCases,\n                                  TestSpec const &testSpec,\n                                  IConfig const &config);\nstd::vector<TestCase> const &getAllTestCasesSorted(IConfig const &config);\n\nclass TestRegistry : public ITestCaseRegistry {\npublic:\n    virtual ~TestRegistry() = default;\n\n    virtual void registerTest(TestCase const &testCase);\n\n    std::vector<TestCase> const &getAllTests() const override;\n    std::vector<TestCase> const &getAllTestsSorted(IConfig const &config) const override;\n\nprivate:\n    std::vector<TestCase> m_functions;\n    mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder;\n    mutable std::vector<TestCase> m_sortedFunctions;\n    std::size_t m_unnamedCount = 0;\n    std::ios_base::Init m_ostreamInit;  // Forces cout/ cerr to be initialised\n};\n\n///////////////////////////////////////////////////////////////////////////\n\nclass TestInvokerAsFunction : public ITestInvoker {\n    void (*m_testAsFunction)();\n\npublic:\n    TestInvokerAsFunction(void (*testAsFunction)()) noexcept;\n\n    void invoke() const override;\n};\n\nstd::string extractClassName(StringRef const &classOrQualifiedMethodName);\n\n///////////////////////////////////////////////////////////////////////////\n\n}  // end namespace Catch\n\n// end catch_test_case_registry_impl.h\n// start catch_reporter_registry.h\n\n#include <map>\n\nnamespace Catch {\n\nclass ReporterRegistry : public IReporterRegistry {\npublic:\n    ~ReporterRegistry() override;\n\n    IStreamingReporterPtr create(std::string const &name, IConfigPtr const &config) const override;\n\n    void registerReporter(std::string const &name, IReporterFactoryPtr const &factory);\n    void registerListener(IReporterFactoryPtr const &factory);\n\n    FactoryMap const &getFactories() const override;\n    Listeners const &getListeners() const override;\n\nprivate:\n    FactoryMap m_factories;\n    Listeners m_listeners;\n};\n}  // namespace Catch\n\n// end catch_reporter_registry.h\n// start catch_tag_alias_registry.h\n\n// start catch_tag_alias.h\n\n#include <string>\n\nnamespace Catch {\n\nstruct TagAlias {\n    TagAlias(std::string const &_tag, SourceLineInfo _lineInfo);\n\n    std::string tag;\n    SourceLineInfo lineInfo;\n};\n\n}  // end namespace Catch\n\n// end catch_tag_alias.h\n#include <map>\n\nnamespace Catch {\n\nclass TagAliasRegistry : public ITagAliasRegistry {\npublic:\n    ~TagAliasRegistry() override;\n    TagAlias const *find(std::string const &alias) const override;\n    std::string expandAliases(std::string const &unexpandedTestSpec) const override;\n    void add(std::string const &alias, std::string const &tag, SourceLineInfo const &lineInfo);\n\nprivate:\n    std::map<std::string, TagAlias> m_registry;\n};\n\n}  // end namespace Catch\n\n// end catch_tag_alias_registry.h\n// start catch_startup_exception_registry.h\n\n#include <exception>\n#include <vector>\n\nnamespace Catch {\n\nclass StartupExceptionRegistry {\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\npublic:\n    void add(std::exception_ptr const &exception) noexcept;\n    std::vector<std::exception_ptr> const &getExceptions() const noexcept;\n\nprivate:\n    std::vector<std::exception_ptr> m_exceptions;\n#endif\n};\n\n}  // end namespace Catch\n\n// end catch_startup_exception_registry.h\n// start catch_singletons.hpp\n\nnamespace Catch {\n\nstruct ISingleton {\n    virtual ~ISingleton();\n};\n\nvoid addSingleton(ISingleton *singleton);\nvoid cleanupSingletons();\n\ntemplate <typename SingletonImplT,\n          typename InterfaceT = SingletonImplT,\n          typename MutableInterfaceT = InterfaceT>\nclass Singleton : SingletonImplT, public ISingleton {\n    static auto getInternal() -> Singleton * {\n        static Singleton *s_instance = nullptr;\n        if (!s_instance) {\n            s_instance = new Singleton;\n            addSingleton(s_instance);\n        }\n        return s_instance;\n    }\n\npublic:\n    static auto get() -> InterfaceT const & { return *getInternal(); }\n    static auto getMutable() -> MutableInterfaceT & { return *getInternal(); }\n};\n\n}  // namespace Catch\n\n// end catch_singletons.hpp\nnamespace Catch {\n\nnamespace {\n\nclass RegistryHub : public IRegistryHub, public IMutableRegistryHub, private NonCopyable {\npublic:  // IRegistryHub\n    RegistryHub() = default;\n    IReporterRegistry const &getReporterRegistry() const override { return m_reporterRegistry; }\n    ITestCaseRegistry const &getTestCaseRegistry() const override { return m_testCaseRegistry; }\n    IExceptionTranslatorRegistry const &getExceptionTranslatorRegistry() const override {\n        return m_exceptionTranslatorRegistry;\n    }\n    ITagAliasRegistry const &getTagAliasRegistry() const override { return m_tagAliasRegistry; }\n    StartupExceptionRegistry const &getStartupExceptionRegistry() const override {\n        return m_exceptionRegistry;\n    }\n\npublic:  // IMutableRegistryHub\n    void registerReporter(std::string const &name, IReporterFactoryPtr const &factory) override {\n        m_reporterRegistry.registerReporter(name, factory);\n    }\n    void registerListener(IReporterFactoryPtr const &factory) override {\n        m_reporterRegistry.registerListener(factory);\n    }\n    void registerTest(TestCase const &testInfo) override {\n        m_testCaseRegistry.registerTest(testInfo);\n    }\n    void registerTranslator(const IExceptionTranslator *translator) override {\n        m_exceptionTranslatorRegistry.registerTranslator(translator);\n    }\n    void registerTagAlias(std::string const &alias,\n                          std::string const &tag,\n                          SourceLineInfo const &lineInfo) override {\n        m_tagAliasRegistry.add(alias, tag, lineInfo);\n    }\n    void registerStartupException() noexcept override {\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n        m_exceptionRegistry.add(std::current_exception());\n#else\n        CATCH_INTERNAL_ERROR(\n                \"Attempted to register active exception under \"\n                \"CATCH_CONFIG_DISABLE_EXCEPTIONS!\");\n#endif\n    }\n    IMutableEnumValuesRegistry &getMutableEnumValuesRegistry() override {\n        return m_enumValuesRegistry;\n    }\n\nprivate:\n    TestRegistry m_testCaseRegistry;\n    ReporterRegistry m_reporterRegistry;\n    ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;\n    TagAliasRegistry m_tagAliasRegistry;\n    StartupExceptionRegistry m_exceptionRegistry;\n    Detail::EnumValuesRegistry m_enumValuesRegistry;\n};\n}  // namespace\n\nusing RegistryHubSingleton = Singleton<RegistryHub, IRegistryHub, IMutableRegistryHub>;\n\nIRegistryHub const &getRegistryHub() { return RegistryHubSingleton::get(); }\nIMutableRegistryHub &getMutableRegistryHub() { return RegistryHubSingleton::getMutable(); }\nvoid cleanUp() {\n    cleanupSingletons();\n    cleanUpContext();\n}\nstd::string translateActiveException() {\n    return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();\n}\n\n}  // end namespace Catch\n// end catch_registry_hub.cpp\n// start catch_reporter_registry.cpp\n\nnamespace Catch {\n\nReporterRegistry::~ReporterRegistry() = default;\n\nIStreamingReporterPtr ReporterRegistry::create(std::string const &name,\n                                               IConfigPtr const &config) const {\n    auto it = m_factories.find(name);\n    if (it == m_factories.end())\n        return nullptr;\n    return it->second->create(ReporterConfig(config));\n}\n\nvoid ReporterRegistry::registerReporter(std::string const &name,\n                                        IReporterFactoryPtr const &factory) {\n    m_factories.emplace(name, factory);\n}\nvoid ReporterRegistry::registerListener(IReporterFactoryPtr const &factory) {\n    m_listeners.push_back(factory);\n}\n\nIReporterRegistry::FactoryMap const &ReporterRegistry::getFactories() const { return m_factories; }\nIReporterRegistry::Listeners const &ReporterRegistry::getListeners() const { return m_listeners; }\n\n}  // namespace Catch\n// end catch_reporter_registry.cpp\n// start catch_result_type.cpp\n\nnamespace Catch {\n\nbool isOk(ResultWas::OfType resultType) { return (resultType & ResultWas::FailureBit) == 0; }\nbool isJustInfo(int flags) { return flags == ResultWas::Info; }\n\nResultDisposition::Flags operator|(ResultDisposition::Flags lhs, ResultDisposition::Flags rhs) {\n    return static_cast<ResultDisposition::Flags>(static_cast<int>(lhs) | static_cast<int>(rhs));\n}\n\nbool shouldContinueOnFailure(int flags) {\n    return (flags & ResultDisposition::ContinueOnFailure) != 0;\n}\nbool shouldSuppressFailure(int flags) { return (flags & ResultDisposition::SuppressFail) != 0; }\n\n}  // end namespace Catch\n// end catch_result_type.cpp\n// start catch_run_context.cpp\n\n#include <algorithm>\n#include <cassert>\n#include <sstream>\n\nnamespace Catch {\n\nnamespace Generators {\nstruct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker {\n    GeneratorBasePtr m_generator;\n\n    GeneratorTracker(TestCaseTracking::NameAndLocation const &nameAndLocation,\n                     TrackerContext &ctx,\n                     ITracker *parent)\n            : TrackerBase(nameAndLocation, ctx, parent) {}\n    ~GeneratorTracker();\n\n    static GeneratorTracker &acquire(TrackerContext &ctx,\n                                     TestCaseTracking::NameAndLocation const &nameAndLocation) {\n        std::shared_ptr<GeneratorTracker> tracker;\n\n        ITracker &currentTracker = ctx.currentTracker();\n        // Under specific circumstances, the generator we want\n        // to acquire is also the current tracker. If this is\n        // the case, we have to avoid looking through current\n        // tracker's children, and instead return the current\n        // tracker.\n        // A case where this check is important is e.g.\n        //     for (int i = 0; i < 5; ++i) {\n        //         int n = GENERATE(1, 2);\n        //     }\n        //\n        // without it, the code above creates 5 nested generators.\n        if (currentTracker.nameAndLocation() == nameAndLocation) {\n            auto thisTracker = currentTracker.parent().findChild(nameAndLocation);\n            assert(thisTracker);\n            assert(thisTracker->isGeneratorTracker());\n            tracker = std::static_pointer_cast<GeneratorTracker>(thisTracker);\n        } else if (TestCaseTracking::ITrackerPtr childTracker =\n                           currentTracker.findChild(nameAndLocation)) {\n            assert(childTracker);\n            assert(childTracker->isGeneratorTracker());\n            tracker = std::static_pointer_cast<GeneratorTracker>(childTracker);\n        } else {\n            tracker = std::make_shared<GeneratorTracker>(nameAndLocation, ctx, &currentTracker);\n            currentTracker.addChild(tracker);\n        }\n\n        if (!tracker->isComplete()) {\n            tracker->open();\n        }\n\n        return *tracker;\n    }\n\n    // TrackerBase interface\n    bool isGeneratorTracker() const override { return true; }\n    auto hasGenerator() const -> bool override { return !!m_generator; }\n    void close() override {\n        TrackerBase::close();\n        // If a generator has a child (it is followed by a section)\n        // and none of its children have started, then we must wait\n        // until later to start consuming its values.\n        // This catches cases where `GENERATE` is placed between two\n        // `SECTION`s.\n        // **The check for m_children.empty cannot be removed**.\n        // doing so would break `GENERATE` _not_ followed by `SECTION`s.\n        const bool should_wait_for_child = [&]() {\n            // No children -> nobody to wait for\n            if (m_children.empty()) {\n                return false;\n            }\n            // If at least one child started executing, don't wait\n            if (std::find_if(m_children.begin(), m_children.end(),\n                             [](TestCaseTracking::ITrackerPtr tracker) {\n                                 return tracker->hasStarted();\n                             }) != m_children.end()) {\n                return false;\n            }\n\n            // No children have started. We need to check if they _can_\n            // start, and thus we should wait for them, or they cannot\n            // start (due to filters), and we shouldn't wait for them\n            auto *parent = m_parent;\n            // This is safe: there is always at least one section\n            // tracker in a test case tracking tree\n            while (!parent->isSectionTracker()) {\n                parent = &(parent->parent());\n            }\n            assert(parent && \"Missing root (test case) level section\");\n\n            auto const &parentSection = static_cast<SectionTracker &>(*parent);\n            auto const &filters = parentSection.getFilters();\n            // No filters -> no restrictions on running sections\n            if (filters.empty()) {\n                return true;\n            }\n\n            for (auto const &child : m_children) {\n                if (child->isSectionTracker() &&\n                    std::find(filters.begin(), filters.end(),\n                              static_cast<SectionTracker &>(*child).trimmedName()) !=\n                            filters.end()) {\n                    return true;\n                }\n            }\n            return false;\n        }();\n\n        // This check is a bit tricky, because m_generator->next()\n        // has a side-effect, where it consumes generator's current\n        // value, but we do not want to invoke the side-effect if\n        // this generator is still waiting for any child to start.\n        if (should_wait_for_child || (m_runState == CompletedSuccessfully && m_generator->next())) {\n            m_children.clear();\n            m_runState = Executing;\n        }\n    }\n\n    // IGeneratorTracker interface\n    auto getGenerator() const -> GeneratorBasePtr const & override { return m_generator; }\n    void setGenerator(GeneratorBasePtr &&generator) override { m_generator = std::move(generator); }\n};\nGeneratorTracker::~GeneratorTracker() {}\n}  // namespace Generators\n\nRunContext::RunContext(IConfigPtr const &_config, IStreamingReporterPtr &&reporter)\n        : m_runInfo(_config->name()),\n          m_context(getCurrentMutableContext()),\n          m_config(_config),\n          m_reporter(std::move(reporter)),\n          m_lastAssertionInfo{StringRef(), SourceLineInfo(\"\", 0), StringRef(),\n                              ResultDisposition::Normal},\n          m_includeSuccessfulResults(m_config->includeSuccessfulResults() ||\n                                     m_reporter->getPreferences().shouldReportAllAssertions) {\n    m_context.setRunner(this);\n    m_context.setConfig(m_config);\n    m_context.setResultCapture(this);\n    m_reporter->testRunStarting(m_runInfo);\n}\n\nRunContext::~RunContext() {\n    m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting()));\n}\n\nvoid RunContext::testGroupStarting(std::string const &testSpec,\n                                   std::size_t groupIndex,\n                                   std::size_t groupsCount) {\n    m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount));\n}\n\nvoid RunContext::testGroupEnded(std::string const &testSpec,\n                                Totals const &totals,\n                                std::size_t groupIndex,\n                                std::size_t groupsCount) {\n    m_reporter->testGroupEnded(\n            TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting()));\n}\n\nTotals RunContext::runTest(TestCase const &testCase) {\n    Totals prevTotals = m_totals;\n\n    std::string redirectedCout;\n    std::string redirectedCerr;\n\n    auto const &testInfo = testCase.getTestCaseInfo();\n\n    m_reporter->testCaseStarting(testInfo);\n\n    m_activeTestCase = &testCase;\n\n    ITracker &rootTracker = m_trackerContext.startRun();\n    assert(rootTracker.isSectionTracker());\n    static_cast<SectionTracker &>(rootTracker).addInitialFilters(m_config->getSectionsToRun());\n    do {\n        m_trackerContext.startCycle();\n        m_testCaseTracker = &SectionTracker::acquire(\n                m_trackerContext,\n                TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo));\n        runCurrentTest(redirectedCout, redirectedCerr);\n    } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting());\n\n    Totals deltaTotals = m_totals.delta(prevTotals);\n    if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) {\n        deltaTotals.assertions.failed++;\n        deltaTotals.testCases.passed--;\n        deltaTotals.testCases.failed++;\n    }\n    m_totals.testCases += deltaTotals.testCases;\n    m_reporter->testCaseEnded(\n            TestCaseStats(testInfo, deltaTotals, redirectedCout, redirectedCerr, aborting()));\n\n    m_activeTestCase = nullptr;\n    m_testCaseTracker = nullptr;\n\n    return deltaTotals;\n}\n\nIConfigPtr RunContext::config() const { return m_config; }\n\nIStreamingReporter &RunContext::reporter() const { return *m_reporter; }\n\nvoid RunContext::assertionEnded(AssertionResult const &result) {\n    if (result.getResultType() == ResultWas::Ok) {\n        m_totals.assertions.passed++;\n        m_lastAssertionPassed = true;\n    } else if (!result.isOk()) {\n        m_lastAssertionPassed = false;\n        if (m_activeTestCase->getTestCaseInfo().okToFail())\n            m_totals.assertions.failedButOk++;\n        else\n            m_totals.assertions.failed++;\n    } else {\n        m_lastAssertionPassed = true;\n    }\n\n    // We have no use for the return value (whether messages should be cleared),\n    // because messages were made scoped and should be let to clear themselves\n    // out.\n    static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals)));\n\n    if (result.getResultType() != ResultWas::Warning)\n        m_messageScopes.clear();\n\n    // Reset working state\n    resetAssertionInfo();\n    m_lastResult = result;\n}\nvoid RunContext::resetAssertionInfo() {\n    m_lastAssertionInfo.macroName = StringRef();\n    m_lastAssertionInfo.capturedExpression = \"{Unknown expression after the reported line}\"_sr;\n}\n\nbool RunContext::sectionStarted(SectionInfo const &sectionInfo, Counts &assertions) {\n    ITracker &sectionTracker = SectionTracker::acquire(\n            m_trackerContext,\n            TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo));\n    if (!sectionTracker.isOpen())\n        return false;\n    m_activeSections.push_back(&sectionTracker);\n\n    m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo;\n\n    m_reporter->sectionStarting(sectionInfo);\n\n    assertions = m_totals.assertions;\n\n    return true;\n}\nauto RunContext::acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const &lineInfo)\n        -> IGeneratorTracker & {\n    using namespace Generators;\n    GeneratorTracker &tracker = GeneratorTracker::acquire(\n            m_trackerContext,\n            TestCaseTracking::NameAndLocation(static_cast<std::string>(generatorName), lineInfo));\n    m_lastAssertionInfo.lineInfo = lineInfo;\n    return tracker;\n}\n\nbool RunContext::testForMissingAssertions(Counts &assertions) {\n    if (assertions.total() != 0)\n        return false;\n    if (!m_config->warnAboutMissingAssertions())\n        return false;\n    if (m_trackerContext.currentTracker().hasChildren())\n        return false;\n    m_totals.assertions.failed++;\n    assertions.failed++;\n    return true;\n}\n\nvoid RunContext::sectionEnded(SectionEndInfo const &endInfo) {\n    Counts assertions = m_totals.assertions - endInfo.prevAssertions;\n    bool missingAssertions = testForMissingAssertions(assertions);\n\n    if (!m_activeSections.empty()) {\n        m_activeSections.back()->close();\n        m_activeSections.pop_back();\n    }\n\n    m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions,\n                                          endInfo.durationInSeconds, missingAssertions));\n    m_messages.clear();\n    m_messageScopes.clear();\n}\n\nvoid RunContext::sectionEndedEarly(SectionEndInfo const &endInfo) {\n    if (m_unfinishedSections.empty())\n        m_activeSections.back()->fail();\n    else\n        m_activeSections.back()->close();\n    m_activeSections.pop_back();\n\n    m_unfinishedSections.push_back(endInfo);\n}\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\nvoid RunContext::benchmarkPreparing(std::string const &name) {\n    m_reporter->benchmarkPreparing(name);\n}\nvoid RunContext::benchmarkStarting(BenchmarkInfo const &info) {\n    m_reporter->benchmarkStarting(info);\n}\nvoid RunContext::benchmarkEnded(BenchmarkStats<> const &stats) {\n    m_reporter->benchmarkEnded(stats);\n}\nvoid RunContext::benchmarkFailed(std::string const &error) { m_reporter->benchmarkFailed(error); }\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\nvoid RunContext::pushScopedMessage(MessageInfo const &message) { m_messages.push_back(message); }\n\nvoid RunContext::popScopedMessage(MessageInfo const &message) {\n    m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end());\n}\n\nvoid RunContext::emplaceUnscopedMessage(MessageBuilder const &builder) {\n    m_messageScopes.emplace_back(builder);\n}\n\nstd::string RunContext::getCurrentTestName() const {\n    return m_activeTestCase ? m_activeTestCase->getTestCaseInfo().name : std::string();\n}\n\nconst AssertionResult *RunContext::getLastResult() const { return &(*m_lastResult); }\n\nvoid RunContext::exceptionEarlyReported() { m_shouldReportUnexpected = false; }\n\nvoid RunContext::handleFatalErrorCondition(StringRef message) {\n    // First notify reporter that bad things happened\n    m_reporter->fatalErrorEncountered(message);\n\n    // Don't rebuild the result -- the stringification itself can cause more fatal\n    // errors Instead, fake a result data.\n    AssertionResultData tempResult(ResultWas::FatalErrorCondition, {false});\n    tempResult.message = static_cast<std::string>(message);\n    AssertionResult result(m_lastAssertionInfo, tempResult);\n\n    assertionEnded(result);\n\n    handleUnfinishedSections();\n\n    // Recreate section for test case (as we will lose the one that was in scope)\n    auto const &testCaseInfo = m_activeTestCase->getTestCaseInfo();\n    SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);\n\n    Counts assertions;\n    assertions.failed = 1;\n    SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false);\n    m_reporter->sectionEnded(testCaseSectionStats);\n\n    auto const &testInfo = m_activeTestCase->getTestCaseInfo();\n\n    Totals deltaTotals;\n    deltaTotals.testCases.failed = 1;\n    deltaTotals.assertions.failed = 1;\n    m_reporter->testCaseEnded(\n            TestCaseStats(testInfo, deltaTotals, std::string(), std::string(), false));\n    m_totals.testCases.failed++;\n    testGroupEnded(std::string(), m_totals, 1, 1);\n    m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false));\n}\n\nbool RunContext::lastAssertionPassed() { return m_lastAssertionPassed; }\n\nvoid RunContext::assertionPassed() {\n    m_lastAssertionPassed = true;\n    ++m_totals.assertions.passed;\n    resetAssertionInfo();\n    m_messageScopes.clear();\n}\n\nbool RunContext::aborting() const {\n    return m_totals.assertions.failed >= static_cast<std::size_t>(m_config->abortAfter());\n}\n\nvoid RunContext::runCurrentTest(std::string &redirectedCout, std::string &redirectedCerr) {\n    auto const &testCaseInfo = m_activeTestCase->getTestCaseInfo();\n    SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);\n    m_reporter->sectionStarting(testCaseSection);\n    Counts prevAssertions = m_totals.assertions;\n    double duration = 0;\n    m_shouldReportUnexpected = true;\n    m_lastAssertionInfo = {\"TEST_CASE\"_sr, testCaseInfo.lineInfo, StringRef(),\n                           ResultDisposition::Normal};\n\n    seedRng(*m_config);\n\n    Timer timer;\n    CATCH_TRY {\n        if (m_reporter->getPreferences().shouldRedirectStdOut) {\n#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)\n            RedirectedStreams redirectedStreams(redirectedCout, redirectedCerr);\n\n            timer.start();\n            invokeActiveTestCase();\n#else\n            OutputRedirect r(redirectedCout, redirectedCerr);\n            timer.start();\n            invokeActiveTestCase();\n#endif\n        } else {\n            timer.start();\n            invokeActiveTestCase();\n        }\n        duration = timer.getElapsedSeconds();\n    }\n    CATCH_CATCH_ANON(TestFailureException &) {\n        // This just means the test was aborted due to failure\n    }\n    CATCH_CATCH_ALL {\n        // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE\n        // assertions are reported without translation at the point of origin.\n        if (m_shouldReportUnexpected) {\n            AssertionReaction dummyReaction;\n            handleUnexpectedInflightException(m_lastAssertionInfo, translateActiveException(),\n                                              dummyReaction);\n        }\n    }\n    Counts assertions = m_totals.assertions - prevAssertions;\n    bool missingAssertions = testForMissingAssertions(assertions);\n\n    m_testCaseTracker->close();\n    handleUnfinishedSections();\n    m_messages.clear();\n    m_messageScopes.clear();\n\n    SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions);\n    m_reporter->sectionEnded(testCaseSectionStats);\n}\n\nvoid RunContext::invokeActiveTestCase() {\n    FatalConditionHandlerGuard _(&m_fatalConditionhandler);\n    m_activeTestCase->invoke();\n}\n\nvoid RunContext::handleUnfinishedSections() {\n    // If sections ended prematurely due to an exception we stored their\n    // infos here so we can tear them down outside the unwind process.\n    for (auto it = m_unfinishedSections.rbegin(), itEnd = m_unfinishedSections.rend(); it != itEnd;\n         ++it)\n        sectionEnded(*it);\n    m_unfinishedSections.clear();\n}\n\nvoid RunContext::handleExpr(AssertionInfo const &info,\n                            ITransientExpression const &expr,\n                            AssertionReaction &reaction) {\n    m_reporter->assertionStarting(info);\n\n    bool negated = isFalseTest(info.resultDisposition);\n    bool result = expr.getResult() != negated;\n\n    if (result) {\n        if (!m_includeSuccessfulResults) {\n            assertionPassed();\n        } else {\n            reportExpr(info, ResultWas::Ok, &expr, negated);\n        }\n    } else {\n        reportExpr(info, ResultWas::ExpressionFailed, &expr, negated);\n        populateReaction(reaction);\n    }\n}\nvoid RunContext::reportExpr(AssertionInfo const &info,\n                            ResultWas::OfType resultType,\n                            ITransientExpression const *expr,\n                            bool negated) {\n    m_lastAssertionInfo = info;\n    AssertionResultData data(resultType, LazyExpression(negated));\n\n    AssertionResult assertionResult{info, data};\n    assertionResult.m_resultData.lazyExpression.m_transientExpression = expr;\n\n    assertionEnded(assertionResult);\n}\n\nvoid RunContext::handleMessage(AssertionInfo const &info,\n                               ResultWas::OfType resultType,\n                               StringRef const &message,\n                               AssertionReaction &reaction) {\n    m_reporter->assertionStarting(info);\n\n    m_lastAssertionInfo = info;\n\n    AssertionResultData data(resultType, LazyExpression(false));\n    data.message = static_cast<std::string>(message);\n    AssertionResult assertionResult{m_lastAssertionInfo, data};\n    assertionEnded(assertionResult);\n    if (!assertionResult.isOk())\n        populateReaction(reaction);\n}\nvoid RunContext::handleUnexpectedExceptionNotThrown(AssertionInfo const &info,\n                                                    AssertionReaction &reaction) {\n    handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction);\n}\n\nvoid RunContext::handleUnexpectedInflightException(AssertionInfo const &info,\n                                                   std::string const &message,\n                                                   AssertionReaction &reaction) {\n    m_lastAssertionInfo = info;\n\n    AssertionResultData data(ResultWas::ThrewException, LazyExpression(false));\n    data.message = message;\n    AssertionResult assertionResult{info, data};\n    assertionEnded(assertionResult);\n    populateReaction(reaction);\n}\n\nvoid RunContext::populateReaction(AssertionReaction &reaction) {\n    reaction.shouldDebugBreak = m_config->shouldDebugBreak();\n    reaction.shouldThrow =\n            aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal);\n}\n\nvoid RunContext::handleIncomplete(AssertionInfo const &info) {\n    m_lastAssertionInfo = info;\n\n    AssertionResultData data(ResultWas::ThrewException, LazyExpression(false));\n    data.message = \"Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE\";\n    AssertionResult assertionResult{info, data};\n    assertionEnded(assertionResult);\n}\nvoid RunContext::handleNonExpr(AssertionInfo const &info,\n                               ResultWas::OfType resultType,\n                               AssertionReaction &reaction) {\n    m_lastAssertionInfo = info;\n\n    AssertionResultData data(resultType, LazyExpression(false));\n    AssertionResult assertionResult{info, data};\n    assertionEnded(assertionResult);\n\n    if (!assertionResult.isOk())\n        populateReaction(reaction);\n}\n\nIResultCapture &getResultCapture() {\n    if (auto *capture = getCurrentContext().getResultCapture())\n        return *capture;\n    else\n        CATCH_INTERNAL_ERROR(\"No result capture instance\");\n}\n\nvoid seedRng(IConfig const &config) {\n    if (config.rngSeed() != 0) {\n        std::srand(config.rngSeed());\n        rng().seed(config.rngSeed());\n    }\n}\n\nunsigned int rngSeed() { return getCurrentContext().getConfig()->rngSeed(); }\n\n}  // namespace Catch\n// end catch_run_context.cpp\n// start catch_section.cpp\n\nnamespace Catch {\n\nSection::Section(SectionInfo const &info)\n        : m_info(info), m_sectionIncluded(getResultCapture().sectionStarted(m_info, m_assertions)) {\n    m_timer.start();\n}\n\nSection::~Section() {\n    if (m_sectionIncluded) {\n        SectionEndInfo endInfo{m_info, m_assertions, m_timer.getElapsedSeconds()};\n        if (uncaught_exceptions())\n            getResultCapture().sectionEndedEarly(endInfo);\n        else\n            getResultCapture().sectionEnded(endInfo);\n    }\n}\n\n// This indicates whether the section should be executed or not\nSection::operator bool() const { return m_sectionIncluded; }\n\n}  // end namespace Catch\n// end catch_section.cpp\n// start catch_section_info.cpp\n\nnamespace Catch {\n\nSectionInfo::SectionInfo(SourceLineInfo const &_lineInfo, std::string const &_name)\n        : name(_name), lineInfo(_lineInfo) {}\n\n}  // end namespace Catch\n// end catch_section_info.cpp\n// start catch_session.cpp\n\n// start catch_session.h\n\n#include <memory>\n\nnamespace Catch {\n\nclass Session : NonCopyable {\npublic:\n    Session();\n    ~Session() override;\n\n    void showHelp() const;\n    void libIdentify();\n\n    int applyCommandLine(int argc, char const *const *argv);\n#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE)\n    int applyCommandLine(int argc, wchar_t const *const *argv);\n#endif\n\n    void useConfigData(ConfigData const &configData);\n\n    template <typename CharT>\n    int run(int argc, CharT const *const argv[]) {\n        if (m_startupExceptions)\n            return 1;\n        int returnCode = applyCommandLine(argc, argv);\n        if (returnCode == 0)\n            returnCode = run();\n        return returnCode;\n    }\n\n    int run();\n\n    clara::Parser const &cli() const;\n    void cli(clara::Parser const &newParser);\n    ConfigData &configData();\n    Config &config();\n\nprivate:\n    int runInternal();\n\n    clara::Parser m_cli;\n    ConfigData m_configData;\n    std::shared_ptr<Config> m_config;\n    bool m_startupExceptions = false;\n};\n\n}  // end namespace Catch\n\n// end catch_session.h\n// start catch_version.h\n\n#include <iosfwd>\n\nnamespace Catch {\n\n// Versioning information\nstruct Version {\n    Version(Version const &) = delete;\n    Version &operator=(Version const &) = delete;\n    Version(unsigned int _majorVersion,\n            unsigned int _minorVersion,\n            unsigned int _patchNumber,\n            char const *const _branchName,\n            unsigned int _buildNumber);\n\n    unsigned int const majorVersion;\n    unsigned int const minorVersion;\n    unsigned int const patchNumber;\n\n    // buildNumber is only used if branchName is not null\n    char const *const branchName;\n    unsigned int const buildNumber;\n\n    friend std::ostream &operator<<(std::ostream &os, Version const &version);\n};\n\nVersion const &libraryVersion();\n}  // namespace Catch\n\n// end catch_version.h\n#include <cstdlib>\n#include <iomanip>\n#include <iterator>\n#include <set>\n\nnamespace Catch {\n\nnamespace {\nconst int MaxExitCode = 255;\n\nIStreamingReporterPtr createReporter(std::string const &reporterName, IConfigPtr const &config) {\n    auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config);\n    CATCH_ENFORCE(reporter, \"No reporter registered with name: '\" << reporterName << \"'\");\n\n    return reporter;\n}\n\nIStreamingReporterPtr makeReporter(std::shared_ptr<Config> const &config) {\n    if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) {\n        return createReporter(config->getReporterName(), config);\n    }\n\n    // On older platforms, returning std::unique_ptr<ListeningReporter>\n    // when the return type is std::unique_ptr<IStreamingReporter>\n    // doesn't compile without a std::move call. However, this causes\n    // a warning on newer platforms. Thus, we have to work around\n    // it a bit and downcast the pointer manually.\n    auto ret = std::unique_ptr<IStreamingReporter>(new ListeningReporter);\n    auto &multi = static_cast<ListeningReporter &>(*ret);\n    auto const &listeners = Catch::getRegistryHub().getReporterRegistry().getListeners();\n    for (auto const &listener : listeners) {\n        multi.addListener(listener->create(Catch::ReporterConfig(config)));\n    }\n    multi.addReporter(createReporter(config->getReporterName(), config));\n    return ret;\n}\n\nclass TestGroup {\npublic:\n    explicit TestGroup(std::shared_ptr<Config> const &config)\n            : m_config{config}, m_context{config, makeReporter(config)} {\n        auto const &allTestCases = getAllTestCasesSorted(*m_config);\n        m_matches = m_config->testSpec().matchesByFilter(allTestCases, *m_config);\n        auto const &invalidArgs = m_config->testSpec().getInvalidArgs();\n\n        if (m_matches.empty() && invalidArgs.empty()) {\n            for (auto const &test : allTestCases)\n                if (!test.isHidden())\n                    m_tests.emplace(&test);\n        } else {\n            for (auto const &match : m_matches)\n                m_tests.insert(match.tests.begin(), match.tests.end());\n        }\n    }\n\n    Totals execute() {\n        auto const &invalidArgs = m_config->testSpec().getInvalidArgs();\n        Totals totals;\n        m_context.testGroupStarting(m_config->name(), 1, 1);\n        for (auto const &testCase : m_tests) {\n            if (!m_context.aborting())\n                totals += m_context.runTest(*testCase);\n            else\n                m_context.reporter().skipTest(*testCase);\n        }\n\n        for (auto const &match : m_matches) {\n            if (match.tests.empty()) {\n                m_context.reporter().noMatchingTestCases(match.name);\n                totals.error = -1;\n            }\n        }\n\n        if (!invalidArgs.empty()) {\n            for (auto const &invalidArg : invalidArgs)\n                m_context.reporter().reportInvalidArguments(invalidArg);\n        }\n\n        m_context.testGroupEnded(m_config->name(), totals, 1, 1);\n        return totals;\n    }\n\nprivate:\n    using Tests = std::set<TestCase const *>;\n\n    std::shared_ptr<Config> m_config;\n    RunContext m_context;\n    Tests m_tests;\n    TestSpec::Matches m_matches;\n};\n\nvoid applyFilenamesAsTags(Catch::IConfig const &config) {\n    auto &tests = const_cast<std::vector<TestCase> &>(getAllTestCasesSorted(config));\n    for (auto &testCase : tests) {\n        auto tags = testCase.tags;\n\n        std::string filename = testCase.lineInfo.file;\n        auto lastSlash = filename.find_last_of(\"\\\\/\");\n        if (lastSlash != std::string::npos) {\n            filename.erase(0, lastSlash);\n            filename[0] = '#';\n        }\n\n        auto lastDot = filename.find_last_of('.');\n        if (lastDot != std::string::npos) {\n            filename.erase(lastDot);\n        }\n\n        tags.push_back(std::move(filename));\n        setTags(testCase, tags);\n    }\n}\n\n}  // namespace\n\nSession::Session() {\n    static bool alreadyInstantiated = false;\n    if (alreadyInstantiated) {\n        CATCH_TRY { CATCH_INTERNAL_ERROR(\"Only one instance of Catch::Session can ever be used\"); }\n        CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); }\n    }\n\n    // There cannot be exceptions at startup in no-exception mode.\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n    const auto &exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions();\n    if (!exceptions.empty()) {\n        config();\n        getCurrentMutableContext().setConfig(m_config);\n\n        m_startupExceptions = true;\n        Colour colourGuard(Colour::Red);\n        Catch::cerr() << \"Errors occurred during startup!\" << '\\n';\n        // iterate over all exceptions and notify user\n        for (const auto &ex_ptr : exceptions) {\n            try {\n                std::rethrow_exception(ex_ptr);\n            } catch (std::exception const &ex) {\n                Catch::cerr() << Column(ex.what()).indent(2) << '\\n';\n            }\n        }\n    }\n#endif\n\n    alreadyInstantiated = true;\n    m_cli = makeCommandLineParser(m_configData);\n}\nSession::~Session() { Catch::cleanUp(); }\n\nvoid Session::showHelp() const {\n    Catch::cout() << \"\\nCatch v\" << libraryVersion() << \"\\n\"\n                  << m_cli << std::endl\n                  << \"For more detailed usage please see the project docs\\n\"\n                  << std::endl;\n}\nvoid Session::libIdentify() {\n    Catch::cout() << std::left << std::setw(16) << \"description: \"\n                  << \"A Catch2 test executable\\n\"\n                  << std::left << std::setw(16) << \"category: \"\n                  << \"testframework\\n\"\n                  << std::left << std::setw(16) << \"framework: \"\n                  << \"Catch Test\\n\"\n                  << std::left << std::setw(16) << \"version: \" << libraryVersion() << std::endl;\n}\n\nint Session::applyCommandLine(int argc, char const *const *argv) {\n    if (m_startupExceptions)\n        return 1;\n\n    auto result = m_cli.parse(clara::Args(argc, argv));\n    if (!result) {\n        config();\n        getCurrentMutableContext().setConfig(m_config);\n        Catch::cerr() << Colour(Colour::Red) << \"\\nError(s) in input:\\n\"\n                      << Column(result.errorMessage()).indent(2) << \"\\n\\n\";\n        Catch::cerr() << \"Run with -? for usage\\n\" << std::endl;\n        return MaxExitCode;\n    }\n\n    if (m_configData.showHelp)\n        showHelp();\n    if (m_configData.libIdentify)\n        libIdentify();\n    m_config.reset();\n    return 0;\n}\n\n#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE)\nint Session::applyCommandLine(int argc, wchar_t const *const *argv) {\n    char **utf8Argv = new char *[argc];\n\n    for (int i = 0; i < argc; ++i) {\n        int bufSize = WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr);\n\n        utf8Argv[i] = new char[bufSize];\n\n        WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, nullptr, nullptr);\n    }\n\n    int returnCode = applyCommandLine(argc, utf8Argv);\n\n    for (int i = 0; i < argc; ++i)\n        delete[] utf8Argv[i];\n\n    delete[] utf8Argv;\n\n    return returnCode;\n}\n#endif\n\nvoid Session::useConfigData(ConfigData const &configData) {\n    m_configData = configData;\n    m_config.reset();\n}\n\nint Session::run() {\n    if ((m_configData.waitForKeypress & WaitForKeypress::BeforeStart) != 0) {\n        Catch::cout() << \"...waiting for enter/ return before starting\" << std::endl;\n        static_cast<void>(std::getchar());\n    }\n    int exitCode = runInternal();\n    if ((m_configData.waitForKeypress & WaitForKeypress::BeforeExit) != 0) {\n        Catch::cout() << \"...waiting for enter/ return before exiting, with code: \" << exitCode\n                      << std::endl;\n        static_cast<void>(std::getchar());\n    }\n    return exitCode;\n}\n\nclara::Parser const &Session::cli() const { return m_cli; }\nvoid Session::cli(clara::Parser const &newParser) { m_cli = newParser; }\nConfigData &Session::configData() { return m_configData; }\nConfig &Session::config() {\n    if (!m_config)\n        m_config = std::make_shared<Config>(m_configData);\n    return *m_config;\n}\n\nint Session::runInternal() {\n    if (m_startupExceptions)\n        return 1;\n\n    if (m_configData.showHelp || m_configData.libIdentify) {\n        return 0;\n    }\n\n    CATCH_TRY {\n        config();  // Force config to be constructed\n\n        seedRng(*m_config);\n\n        if (m_configData.filenamesAsTags)\n            applyFilenamesAsTags(*m_config);\n\n        // Handle list request\n        if (Option<std::size_t> listed = list(m_config))\n            return static_cast<int>(*listed);\n\n        TestGroup tests{m_config};\n        auto const totals = tests.execute();\n\n        if (m_config->warnAboutNoTests() && totals.error == -1)\n            return 2;\n\n        // Note that on unices only the lower 8 bits are usually used, clamping\n        // the return value to 255 prevents false negative when some multiple\n        // of 256 tests has failed\n        return (std::min)(MaxExitCode,\n                          (std::max)(totals.error, static_cast<int>(totals.assertions.failed)));\n    }\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n    catch (std::exception &ex) {\n        Catch::cerr() << ex.what() << std::endl;\n        return MaxExitCode;\n    }\n#endif\n}\n\n}  // end namespace Catch\n// end catch_session.cpp\n// start catch_singletons.cpp\n\n#include <vector>\n\nnamespace Catch {\n\nnamespace {\nstatic auto getSingletons() -> std::vector<ISingleton *> *& {\n    static std::vector<ISingleton *> *g_singletons = nullptr;\n    if (!g_singletons)\n        g_singletons = new std::vector<ISingleton *>();\n    return g_singletons;\n}\n}  // namespace\n\nISingleton::~ISingleton() {}\n\nvoid addSingleton(ISingleton *singleton) { getSingletons()->push_back(singleton); }\nvoid cleanupSingletons() {\n    auto &singletons = getSingletons();\n    for (auto singleton : *singletons)\n        delete singleton;\n    delete singletons;\n    singletons = nullptr;\n}\n\n}  // namespace Catch\n// end catch_singletons.cpp\n// start catch_startup_exception_registry.cpp\n\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\nnamespace Catch {\nvoid StartupExceptionRegistry::add(std::exception_ptr const &exception) noexcept {\n    CATCH_TRY { m_exceptions.push_back(exception); }\n    CATCH_CATCH_ALL {\n        // If we run out of memory during start-up there's really not a lot more we\n        // can do about it\n        std::terminate();\n    }\n}\n\nstd::vector<std::exception_ptr> const &StartupExceptionRegistry::getExceptions() const noexcept {\n    return m_exceptions;\n}\n\n}  // end namespace Catch\n#endif\n// end catch_startup_exception_registry.cpp\n// start catch_stream.cpp\n\n#include <cstdio>\n#include <fstream>\n#include <iostream>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace Catch {\n\nCatch::IStream::~IStream() = default;\n\nnamespace Detail {\nnamespace {\ntemplate <typename WriterF, std::size_t bufferSize = 256>\nclass StreamBufImpl : public std::streambuf {\n    char data[bufferSize];\n    WriterF m_writer;\n\npublic:\n    StreamBufImpl() { setp(data, data + sizeof(data)); }\n\n    ~StreamBufImpl() noexcept { StreamBufImpl::sync(); }\n\nprivate:\n    int overflow(int c) override {\n        sync();\n\n        if (c != EOF) {\n            if (pbase() == epptr())\n                m_writer(std::string(1, static_cast<char>(c)));\n            else\n                sputc(static_cast<char>(c));\n        }\n        return 0;\n    }\n\n    int sync() override {\n        if (pbase() != pptr()) {\n            m_writer(std::string(pbase(), static_cast<std::string::size_type>(pptr() - pbase())));\n            setp(pbase(), epptr());\n        }\n        return 0;\n    }\n};\n\n///////////////////////////////////////////////////////////////////////////\n\nstruct OutputDebugWriter {\n    void operator()(std::string const &str) { writeToDebugConsole(str); }\n};\n\n///////////////////////////////////////////////////////////////////////////\n\nclass FileStream : public IStream {\n    mutable std::ofstream m_ofs;\n\npublic:\n    FileStream(StringRef filename) {\n        m_ofs.open(filename.c_str());\n        CATCH_ENFORCE(!m_ofs.fail(), \"Unable to open file: '\" << filename << \"'\");\n    }\n    ~FileStream() override = default;\n\npublic:  // IStream\n    std::ostream &stream() const override { return m_ofs; }\n};\n\n///////////////////////////////////////////////////////////////////////////\n\nclass CoutStream : public IStream {\n    mutable std::ostream m_os;\n\npublic:\n    // Store the streambuf from cout up-front because\n    // cout may get redirected when running tests\n    CoutStream() : m_os(Catch::cout().rdbuf()) {}\n    ~CoutStream() override = default;\n\npublic:  // IStream\n    std::ostream &stream() const override { return m_os; }\n};\n\n///////////////////////////////////////////////////////////////////////////\n\nclass DebugOutStream : public IStream {\n    std::unique_ptr<StreamBufImpl<OutputDebugWriter>> m_streamBuf;\n    mutable std::ostream m_os;\n\npublic:\n    DebugOutStream()\n            : m_streamBuf(new StreamBufImpl<OutputDebugWriter>()), m_os(m_streamBuf.get()) {}\n\n    ~DebugOutStream() override = default;\n\npublic:  // IStream\n    std::ostream &stream() const override { return m_os; }\n};\n\n}  // namespace\n}  // namespace Detail\n\n///////////////////////////////////////////////////////////////////////////\n\nauto makeStream(StringRef const &filename) -> IStream const * {\n    if (filename.empty())\n        return new Detail::CoutStream();\n    else if (filename[0] == '%') {\n        if (filename == \"%debug\")\n            return new Detail::DebugOutStream();\n        else\n            CATCH_ERROR(\"Unrecognised stream: '\" << filename << \"'\");\n    } else\n        return new Detail::FileStream(filename);\n}\n\n// This class encapsulates the idea of a pool of ostringstreams that can be\n// reused.\nstruct StringStreams {\n    std::vector<std::unique_ptr<std::ostringstream>> m_streams;\n    std::vector<std::size_t> m_unused;\n    std::ostringstream m_referenceStream;  // Used for copy state/ flags from\n\n    auto add() -> std::size_t {\n        if (m_unused.empty()) {\n            m_streams.push_back(std::unique_ptr<std::ostringstream>(new std::ostringstream));\n            return m_streams.size() - 1;\n        } else {\n            auto index = m_unused.back();\n            m_unused.pop_back();\n            return index;\n        }\n    }\n\n    void release(std::size_t index) {\n        m_streams[index]->copyfmt(m_referenceStream);  // Restore initial flags and other state\n        m_unused.push_back(index);\n    }\n};\n\nReusableStringStream::ReusableStringStream()\n        : m_index(Singleton<StringStreams>::getMutable().add()),\n          m_oss(Singleton<StringStreams>::getMutable().m_streams[m_index].get()) {}\n\nReusableStringStream::~ReusableStringStream() {\n    static_cast<std::ostringstream *>(m_oss)->str(\"\");\n    m_oss->clear();\n    Singleton<StringStreams>::getMutable().release(m_index);\n}\n\nauto ReusableStringStream::str() const -> std::string {\n    return static_cast<std::ostringstream *>(m_oss)->str();\n}\n\n///////////////////////////////////////////////////////////////////////////\n\n#ifndef CATCH_CONFIG_NOSTDOUT  // If you #define this you must implement these \\\n                               // functions\nstd::ostream &cout() { return std::cout; }\nstd::ostream &cerr() { return std::cerr; }\nstd::ostream &clog() { return std::clog; }\n#endif\n}  // namespace Catch\n// end catch_stream.cpp\n// start catch_string_manip.cpp\n\n#include <algorithm>\n#include <cctype>\n#include <cstring>\n#include <ostream>\n#include <vector>\n\nnamespace Catch {\n\nnamespace {\nchar toLowerCh(char c) { return static_cast<char>(std::tolower(static_cast<unsigned char>(c))); }\n}  // namespace\n\nbool startsWith(std::string const &s, std::string const &prefix) {\n    return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin());\n}\nbool startsWith(std::string const &s, char prefix) { return !s.empty() && s[0] == prefix; }\nbool endsWith(std::string const &s, std::string const &suffix) {\n    return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin());\n}\nbool endsWith(std::string const &s, char suffix) { return !s.empty() && s[s.size() - 1] == suffix; }\nbool contains(std::string const &s, std::string const &infix) {\n    return s.find(infix) != std::string::npos;\n}\nvoid toLowerInPlace(std::string &s) { std::transform(s.begin(), s.end(), s.begin(), toLowerCh); }\nstd::string toLower(std::string const &s) {\n    std::string lc = s;\n    toLowerInPlace(lc);\n    return lc;\n}\nstd::string trim(std::string const &str) {\n    static char const *whitespaceChars = \"\\n\\r\\t \";\n    std::string::size_type start = str.find_first_not_of(whitespaceChars);\n    std::string::size_type end = str.find_last_not_of(whitespaceChars);\n\n    return start != std::string::npos ? str.substr(start, 1 + end - start) : std::string();\n}\n\nStringRef trim(StringRef ref) {\n    const auto is_ws = [](char c) { return c == ' ' || c == '\\t' || c == '\\n' || c == '\\r'; };\n    size_t real_begin = 0;\n    while (real_begin < ref.size() && is_ws(ref[real_begin])) {\n        ++real_begin;\n    }\n    size_t real_end = ref.size();\n    while (real_end > real_begin && is_ws(ref[real_end - 1])) {\n        --real_end;\n    }\n\n    return ref.substr(real_begin, real_end - real_begin);\n}\n\nbool replaceInPlace(std::string &str, std::string const &replaceThis, std::string const &withThis) {\n    bool replaced = false;\n    std::size_t i = str.find(replaceThis);\n    while (i != std::string::npos) {\n        replaced = true;\n        str = str.substr(0, i) + withThis + str.substr(i + replaceThis.size());\n        if (i < str.size() - withThis.size())\n            i = str.find(replaceThis, i + withThis.size());\n        else\n            i = std::string::npos;\n    }\n    return replaced;\n}\n\nstd::vector<StringRef> splitStringRef(StringRef str, char delimiter) {\n    std::vector<StringRef> subStrings;\n    std::size_t start = 0;\n    for (std::size_t pos = 0; pos < str.size(); ++pos) {\n        if (str[pos] == delimiter) {\n            if (pos - start > 1)\n                subStrings.push_back(str.substr(start, pos - start));\n            start = pos + 1;\n        }\n    }\n    if (start < str.size())\n        subStrings.push_back(str.substr(start, str.size() - start));\n    return subStrings;\n}\n\npluralise::pluralise(std::size_t count, std::string const &label)\n        : m_count(count), m_label(label) {}\n\nstd::ostream &operator<<(std::ostream &os, pluralise const &pluraliser) {\n    os << pluraliser.m_count << ' ' << pluraliser.m_label;\n    if (pluraliser.m_count != 1)\n        os << 's';\n    return os;\n}\n\n}  // namespace Catch\n// end catch_string_manip.cpp\n// start catch_stringref.cpp\n\n#include <algorithm>\n#include <cstdint>\n#include <cstring>\n#include <ostream>\n\nnamespace Catch {\nStringRef::StringRef(char const *rawChars) noexcept\n        : StringRef(rawChars, static_cast<StringRef::size_type>(std::strlen(rawChars))) {}\n\nauto StringRef::c_str() const -> char const * {\n    CATCH_ENFORCE(isNullTerminated(),\n                  \"Called StringRef::c_str() on a non-null-terminated instance\");\n    return m_start;\n}\nauto StringRef::data() const noexcept -> char const * { return m_start; }\n\nauto StringRef::substr(size_type start, size_type size) const noexcept -> StringRef {\n    if (start < m_size) {\n        return StringRef(m_start + start, (std::min)(m_size - start, size));\n    } else {\n        return StringRef();\n    }\n}\nauto StringRef::operator==(StringRef const &other) const noexcept -> bool {\n    return m_size == other.m_size && (std::memcmp(m_start, other.m_start, m_size) == 0);\n}\n\nauto operator<<(std::ostream &os, StringRef const &str) -> std::ostream & {\n    return os.write(str.data(), str.size());\n}\n\nauto operator+=(std::string &lhs, StringRef const &rhs) -> std::string & {\n    lhs.append(rhs.data(), rhs.size());\n    return lhs;\n}\n\n}  // namespace Catch\n// end catch_stringref.cpp\n// start catch_tag_alias.cpp\n\nnamespace Catch {\nTagAlias::TagAlias(std::string const &_tag, SourceLineInfo _lineInfo)\n        : tag(_tag), lineInfo(_lineInfo) {}\n}  // namespace Catch\n// end catch_tag_alias.cpp\n// start catch_tag_alias_autoregistrar.cpp\n\nnamespace Catch {\n\nRegistrarForTagAliases::RegistrarForTagAliases(char const *alias,\n                                               char const *tag,\n                                               SourceLineInfo const &lineInfo) {\n    CATCH_TRY { getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); }\n    CATCH_CATCH_ALL {\n        // Do not throw when constructing global objects, instead register the\n        // exception to be processed later\n        getMutableRegistryHub().registerStartupException();\n    }\n}\n\n}  // namespace Catch\n// end catch_tag_alias_autoregistrar.cpp\n// start catch_tag_alias_registry.cpp\n\n#include <sstream>\n\nnamespace Catch {\n\nTagAliasRegistry::~TagAliasRegistry() {}\n\nTagAlias const *TagAliasRegistry::find(std::string const &alias) const {\n    auto it = m_registry.find(alias);\n    if (it != m_registry.end())\n        return &(it->second);\n    else\n        return nullptr;\n}\n\nstd::string TagAliasRegistry::expandAliases(std::string const &unexpandedTestSpec) const {\n    std::string expandedTestSpec = unexpandedTestSpec;\n    for (auto const &registryKvp : m_registry) {\n        std::size_t pos = expandedTestSpec.find(registryKvp.first);\n        if (pos != std::string::npos) {\n            expandedTestSpec = expandedTestSpec.substr(0, pos) + registryKvp.second.tag +\n                               expandedTestSpec.substr(pos + registryKvp.first.size());\n        }\n    }\n    return expandedTestSpec;\n}\n\nvoid TagAliasRegistry::add(std::string const &alias,\n                           std::string const &tag,\n                           SourceLineInfo const &lineInfo) {\n    CATCH_ENFORCE(startsWith(alias, \"[@\") && endsWith(alias, ']'),\n                  \"error: tag alias, '\" << alias << \"' is not of the form [@alias name].\\n\"\n                                        << lineInfo);\n\n    CATCH_ENFORCE(m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second,\n                  \"error: tag alias, '\" << alias << \"' already registered.\\n\"\n                                        << \"\\tFirst seen at: \" << find(alias)->lineInfo << \"\\n\"\n                                        << \"\\tRedefined at: \" << lineInfo);\n}\n\nITagAliasRegistry::~ITagAliasRegistry() {}\n\nITagAliasRegistry const &ITagAliasRegistry::get() { return getRegistryHub().getTagAliasRegistry(); }\n\n}  // end namespace Catch\n// end catch_tag_alias_registry.cpp\n// start catch_test_case_info.cpp\n\n#include <algorithm>\n#include <cctype>\n#include <exception>\n#include <sstream>\n\nnamespace Catch {\n\nnamespace {\nTestCaseInfo::SpecialProperties parseSpecialTag(std::string const &tag) {\n    if (startsWith(tag, '.') || tag == \"!hide\")\n        return TestCaseInfo::IsHidden;\n    else if (tag == \"!throws\")\n        return TestCaseInfo::Throws;\n    else if (tag == \"!shouldfail\")\n        return TestCaseInfo::ShouldFail;\n    else if (tag == \"!mayfail\")\n        return TestCaseInfo::MayFail;\n    else if (tag == \"!nonportable\")\n        return TestCaseInfo::NonPortable;\n    else if (tag == \"!benchmark\")\n        return static_cast<TestCaseInfo::SpecialProperties>(TestCaseInfo::Benchmark |\n                                                            TestCaseInfo::IsHidden);\n    else\n        return TestCaseInfo::None;\n}\nbool isReservedTag(std::string const &tag) {\n    return parseSpecialTag(tag) == TestCaseInfo::None && tag.size() > 0 &&\n           !std::isalnum(static_cast<unsigned char>(tag[0]));\n}\nvoid enforceNotReservedTag(std::string const &tag, SourceLineInfo const &_lineInfo) {\n    CATCH_ENFORCE(!isReservedTag(tag), \"Tag name: [\" << tag << \"] is not allowed.\\n\"\n                                                     << \"Tag names starting with non alphanumeric \"\n                                                        \"characters are reserved\\n\"\n                                                     << _lineInfo);\n}\n}  // namespace\n\nTestCase makeTestCase(ITestInvoker *_testCase,\n                      std::string const &_className,\n                      NameAndTags const &nameAndTags,\n                      SourceLineInfo const &_lineInfo) {\n    bool isHidden = false;\n\n    // Parse out tags\n    std::vector<std::string> tags;\n    std::string desc, tag;\n    bool inTag = false;\n    for (char c : nameAndTags.tags) {\n        if (!inTag) {\n            if (c == '[')\n                inTag = true;\n            else\n                desc += c;\n        } else {\n            if (c == ']') {\n                TestCaseInfo::SpecialProperties prop = parseSpecialTag(tag);\n                if ((prop & TestCaseInfo::IsHidden) != 0)\n                    isHidden = true;\n                else if (prop == TestCaseInfo::None)\n                    enforceNotReservedTag(tag, _lineInfo);\n\n                // Merged hide tags like `[.approvals]` should be added as\n                // `[.][approvals]`. The `[.]` is added at later point, so\n                // we only strip the prefix\n                if (startsWith(tag, '.') && tag.size() > 1) {\n                    tag.erase(0, 1);\n                }\n                tags.push_back(tag);\n                tag.clear();\n                inTag = false;\n            } else\n                tag += c;\n        }\n    }\n    if (isHidden) {\n        // Add all \"hidden\" tags to make them behave identically\n        tags.insert(tags.end(), {\".\", \"!hide\"});\n    }\n\n    TestCaseInfo info(static_cast<std::string>(nameAndTags.name), _className, desc, tags,\n                      _lineInfo);\n    return TestCase(_testCase, std::move(info));\n}\n\nvoid setTags(TestCaseInfo &testCaseInfo, std::vector<std::string> tags) {\n    std::sort(begin(tags), end(tags));\n    tags.erase(std::unique(begin(tags), end(tags)), end(tags));\n    testCaseInfo.lcaseTags.clear();\n\n    for (auto const &tag : tags) {\n        std::string lcaseTag = toLower(tag);\n        testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>(\n                testCaseInfo.properties | parseSpecialTag(lcaseTag));\n        testCaseInfo.lcaseTags.push_back(lcaseTag);\n    }\n    testCaseInfo.tags = std::move(tags);\n}\n\nTestCaseInfo::TestCaseInfo(std::string const &_name,\n                           std::string const &_className,\n                           std::string const &_description,\n                           std::vector<std::string> const &_tags,\n                           SourceLineInfo const &_lineInfo)\n        : name(_name),\n          className(_className),\n          description(_description),\n          lineInfo(_lineInfo),\n          properties(None) {\n    setTags(*this, _tags);\n}\n\nbool TestCaseInfo::isHidden() const { return (properties & IsHidden) != 0; }\nbool TestCaseInfo::throws() const { return (properties & Throws) != 0; }\nbool TestCaseInfo::okToFail() const { return (properties & (ShouldFail | MayFail)) != 0; }\nbool TestCaseInfo::expectedToFail() const { return (properties & (ShouldFail)) != 0; }\n\nstd::string TestCaseInfo::tagsAsString() const {\n    std::string ret;\n    // '[' and ']' per tag\n    std::size_t full_size = 2 * tags.size();\n    for (const auto &tag : tags) {\n        full_size += tag.size();\n    }\n    ret.reserve(full_size);\n    for (const auto &tag : tags) {\n        ret.push_back('[');\n        ret.append(tag);\n        ret.push_back(']');\n    }\n\n    return ret;\n}\n\nTestCase::TestCase(ITestInvoker *testCase, TestCaseInfo &&info)\n        : TestCaseInfo(std::move(info)), test(testCase) {}\n\nTestCase TestCase::withName(std::string const &_newName) const {\n    TestCase other(*this);\n    other.name = _newName;\n    return other;\n}\n\nvoid TestCase::invoke() const { test->invoke(); }\n\nbool TestCase::operator==(TestCase const &other) const {\n    return test.get() == other.test.get() && name == other.name && className == other.className;\n}\n\nbool TestCase::operator<(TestCase const &other) const { return name < other.name; }\n\nTestCaseInfo const &TestCase::getTestCaseInfo() const { return *this; }\n\n}  // end namespace Catch\n// end catch_test_case_info.cpp\n// start catch_test_case_registry_impl.cpp\n\n#include <algorithm>\n#include <sstream>\n\nnamespace Catch {\n\nnamespace {\nstruct TestHasher {\n    using hash_t = uint64_t;\n\n    explicit TestHasher(hash_t hashSuffix) : m_hashSuffix{hashSuffix} {}\n\n    uint32_t operator()(TestCase const &t) const {\n        // FNV-1a hash with multiplication fold.\n        const hash_t prime = 1099511628211u;\n        hash_t hash = 14695981039346656037u;\n        for (const char c : t.name) {\n            hash ^= c;\n            hash *= prime;\n        }\n        hash ^= m_hashSuffix;\n        hash *= prime;\n        const uint32_t low{static_cast<uint32_t>(hash)};\n        const uint32_t high{static_cast<uint32_t>(hash >> 32)};\n        return low * high;\n    }\n\nprivate:\n    hash_t m_hashSuffix;\n};\n}  // end unnamed namespace\n\nstd::vector<TestCase> sortTests(IConfig const &config,\n                                std::vector<TestCase> const &unsortedTestCases) {\n    switch (config.runOrder()) {\n    case RunTests::InDeclarationOrder:\n        // already in declaration order\n        break;\n\n    case RunTests::InLexicographicalOrder: {\n        std::vector<TestCase> sorted = unsortedTestCases;\n        std::sort(sorted.begin(), sorted.end());\n        return sorted;\n    }\n\n    case RunTests::InRandomOrder: {\n        seedRng(config);\n        TestHasher h{config.rngSeed()};\n\n        using hashedTest = std::pair<TestHasher::hash_t, TestCase const *>;\n        std::vector<hashedTest> indexed_tests;\n        indexed_tests.reserve(unsortedTestCases.size());\n\n        for (auto const &testCase : unsortedTestCases) {\n            indexed_tests.emplace_back(h(testCase), &testCase);\n        }\n\n        std::sort(indexed_tests.begin(), indexed_tests.end(),\n                  [](hashedTest const &lhs, hashedTest const &rhs) {\n                      if (lhs.first == rhs.first) {\n                          return lhs.second->name < rhs.second->name;\n                      }\n                      return lhs.first < rhs.first;\n                  });\n\n        std::vector<TestCase> sorted;\n        sorted.reserve(indexed_tests.size());\n\n        for (auto const &hashed : indexed_tests) {\n            sorted.emplace_back(*hashed.second);\n        }\n\n        return sorted;\n    }\n    }\n    return unsortedTestCases;\n}\n\nbool isThrowSafe(TestCase const &testCase, IConfig const &config) {\n    return !testCase.throws() || config.allowThrows();\n}\n\nbool matchTest(TestCase const &testCase, TestSpec const &testSpec, IConfig const &config) {\n    return testSpec.matches(testCase) && isThrowSafe(testCase, config);\n}\n\nvoid enforceNoDuplicateTestCases(std::vector<TestCase> const &functions) {\n    std::set<TestCase> seenFunctions;\n    for (auto const &function : functions) {\n        auto prev = seenFunctions.insert(function);\n        CATCH_ENFORCE(prev.second, \"error: TEST_CASE( \\\"\"\n                                           << function.name << \"\\\" ) already defined.\\n\"\n                                           << \"\\tFirst seen at \"\n                                           << prev.first->getTestCaseInfo().lineInfo << \"\\n\"\n                                           << \"\\tRedefined at \"\n                                           << function.getTestCaseInfo().lineInfo);\n    }\n}\n\nstd::vector<TestCase> filterTests(std::vector<TestCase> const &testCases,\n                                  TestSpec const &testSpec,\n                                  IConfig const &config) {\n    std::vector<TestCase> filtered;\n    filtered.reserve(testCases.size());\n    for (auto const &testCase : testCases) {\n        if ((!testSpec.hasFilters() && !testCase.isHidden()) ||\n            (testSpec.hasFilters() && matchTest(testCase, testSpec, config))) {\n            filtered.push_back(testCase);\n        }\n    }\n    return filtered;\n}\nstd::vector<TestCase> const &getAllTestCasesSorted(IConfig const &config) {\n    return getRegistryHub().getTestCaseRegistry().getAllTestsSorted(config);\n}\n\nvoid TestRegistry::registerTest(TestCase const &testCase) {\n    std::string name = testCase.getTestCaseInfo().name;\n    if (name.empty()) {\n        ReusableStringStream rss;\n        rss << \"Anonymous test case \" << ++m_unnamedCount;\n        return registerTest(testCase.withName(rss.str()));\n    }\n    m_functions.push_back(testCase);\n}\n\nstd::vector<TestCase> const &TestRegistry::getAllTests() const { return m_functions; }\nstd::vector<TestCase> const &TestRegistry::getAllTestsSorted(IConfig const &config) const {\n    if (m_sortedFunctions.empty())\n        enforceNoDuplicateTestCases(m_functions);\n\n    if (m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty()) {\n        m_sortedFunctions = sortTests(config, m_functions);\n        m_currentSortOrder = config.runOrder();\n    }\n    return m_sortedFunctions;\n}\n\n///////////////////////////////////////////////////////////////////////////\nTestInvokerAsFunction::TestInvokerAsFunction(void (*testAsFunction)()) noexcept\n        : m_testAsFunction(testAsFunction) {}\n\nvoid TestInvokerAsFunction::invoke() const { m_testAsFunction(); }\n\nstd::string extractClassName(StringRef const &classOrQualifiedMethodName) {\n    std::string className(classOrQualifiedMethodName);\n    if (startsWith(className, '&')) {\n        std::size_t lastColons = className.rfind(\"::\");\n        std::size_t penultimateColons = className.rfind(\"::\", lastColons - 1);\n        if (penultimateColons == std::string::npos)\n            penultimateColons = 1;\n        className = className.substr(penultimateColons, lastColons - penultimateColons);\n    }\n    return className;\n}\n\n}  // end namespace Catch\n// end catch_test_case_registry_impl.cpp\n// start catch_test_case_tracker.cpp\n\n#include <algorithm>\n#include <cassert>\n#include <memory>\n#include <sstream>\n#include <stdexcept>\n\n#if defined(__clang__)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n#endif\n\nnamespace Catch {\nnamespace TestCaseTracking {\n\nNameAndLocation::NameAndLocation(std::string const &_name, SourceLineInfo const &_location)\n        : name(_name), location(_location) {}\n\nITracker::~ITracker() = default;\n\nITracker &TrackerContext::startRun() {\n    m_rootTracker = std::make_shared<SectionTracker>(\n            NameAndLocation(\"{root}\", CATCH_INTERNAL_LINEINFO), *this, nullptr);\n    m_currentTracker = nullptr;\n    m_runState = Executing;\n    return *m_rootTracker;\n}\n\nvoid TrackerContext::endRun() {\n    m_rootTracker.reset();\n    m_currentTracker = nullptr;\n    m_runState = NotStarted;\n}\n\nvoid TrackerContext::startCycle() {\n    m_currentTracker = m_rootTracker.get();\n    m_runState = Executing;\n}\nvoid TrackerContext::completeCycle() { m_runState = CompletedCycle; }\n\nbool TrackerContext::completedCycle() const { return m_runState == CompletedCycle; }\nITracker &TrackerContext::currentTracker() { return *m_currentTracker; }\nvoid TrackerContext::setCurrentTracker(ITracker *tracker) { m_currentTracker = tracker; }\n\nTrackerBase::TrackerBase(NameAndLocation const &nameAndLocation,\n                         TrackerContext &ctx,\n                         ITracker *parent)\n        : ITracker(nameAndLocation), m_ctx(ctx), m_parent(parent) {}\n\nbool TrackerBase::isComplete() const {\n    return m_runState == CompletedSuccessfully || m_runState == Failed;\n}\nbool TrackerBase::isSuccessfullyCompleted() const { return m_runState == CompletedSuccessfully; }\nbool TrackerBase::isOpen() const { return m_runState != NotStarted && !isComplete(); }\nbool TrackerBase::hasChildren() const { return !m_children.empty(); }\n\nvoid TrackerBase::addChild(ITrackerPtr const &child) { m_children.push_back(child); }\n\nITrackerPtr TrackerBase::findChild(NameAndLocation const &nameAndLocation) {\n    auto it = std::find_if(\n            m_children.begin(), m_children.end(), [&nameAndLocation](ITrackerPtr const &tracker) {\n                return tracker->nameAndLocation().location == nameAndLocation.location &&\n                       tracker->nameAndLocation().name == nameAndLocation.name;\n            });\n    return (it != m_children.end()) ? *it : nullptr;\n}\nITracker &TrackerBase::parent() {\n    assert(m_parent);  // Should always be non-null except for root\n    return *m_parent;\n}\n\nvoid TrackerBase::openChild() {\n    if (m_runState != ExecutingChildren) {\n        m_runState = ExecutingChildren;\n        if (m_parent)\n            m_parent->openChild();\n    }\n}\n\nbool TrackerBase::isSectionTracker() const { return false; }\nbool TrackerBase::isGeneratorTracker() const { return false; }\n\nvoid TrackerBase::open() {\n    m_runState = Executing;\n    moveToThis();\n    if (m_parent)\n        m_parent->openChild();\n}\n\nvoid TrackerBase::close() {\n    // Close any still open children (e.g. generators)\n    while (&m_ctx.currentTracker() != this)\n        m_ctx.currentTracker().close();\n\n    switch (m_runState) {\n    case NeedsAnotherRun:\n        break;\n\n    case Executing:\n        m_runState = CompletedSuccessfully;\n        break;\n    case ExecutingChildren:\n        if (std::all_of(m_children.begin(), m_children.end(),\n                        [](ITrackerPtr const &t) { return t->isComplete(); }))\n            m_runState = CompletedSuccessfully;\n        break;\n\n    case NotStarted:\n    case CompletedSuccessfully:\n    case Failed:\n        CATCH_INTERNAL_ERROR(\"Illogical state: \" << m_runState);\n\n    default:\n        CATCH_INTERNAL_ERROR(\"Unknown state: \" << m_runState);\n    }\n    moveToParent();\n    m_ctx.completeCycle();\n}\nvoid TrackerBase::fail() {\n    m_runState = Failed;\n    if (m_parent)\n        m_parent->markAsNeedingAnotherRun();\n    moveToParent();\n    m_ctx.completeCycle();\n}\nvoid TrackerBase::markAsNeedingAnotherRun() { m_runState = NeedsAnotherRun; }\n\nvoid TrackerBase::moveToParent() {\n    assert(m_parent);\n    m_ctx.setCurrentTracker(m_parent);\n}\nvoid TrackerBase::moveToThis() { m_ctx.setCurrentTracker(this); }\n\nSectionTracker::SectionTracker(NameAndLocation const &nameAndLocation,\n                               TrackerContext &ctx,\n                               ITracker *parent)\n        : TrackerBase(nameAndLocation, ctx, parent), m_trimmed_name(trim(nameAndLocation.name)) {\n    if (parent) {\n        while (!parent->isSectionTracker())\n            parent = &parent->parent();\n\n        SectionTracker &parentSection = static_cast<SectionTracker &>(*parent);\n        addNextFilters(parentSection.m_filters);\n    }\n}\n\nbool SectionTracker::isComplete() const {\n    bool complete = true;\n\n    if (m_filters.empty() || m_filters[0] == \"\" ||\n        std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) {\n        complete = TrackerBase::isComplete();\n    }\n    return complete;\n}\n\nbool SectionTracker::isSectionTracker() const { return true; }\n\nSectionTracker &SectionTracker::acquire(TrackerContext &ctx,\n                                        NameAndLocation const &nameAndLocation) {\n    std::shared_ptr<SectionTracker> section;\n\n    ITracker &currentTracker = ctx.currentTracker();\n    if (ITrackerPtr childTracker = currentTracker.findChild(nameAndLocation)) {\n        assert(childTracker);\n        assert(childTracker->isSectionTracker());\n        section = std::static_pointer_cast<SectionTracker>(childTracker);\n    } else {\n        section = std::make_shared<SectionTracker>(nameAndLocation, ctx, &currentTracker);\n        currentTracker.addChild(section);\n    }\n    if (!ctx.completedCycle())\n        section->tryOpen();\n    return *section;\n}\n\nvoid SectionTracker::tryOpen() {\n    if (!isComplete())\n        open();\n}\n\nvoid SectionTracker::addInitialFilters(std::vector<std::string> const &filters) {\n    if (!filters.empty()) {\n        m_filters.reserve(m_filters.size() + filters.size() + 2);\n        m_filters.emplace_back(\"\");  // Root - should never be consulted\n        m_filters.emplace_back(\"\");  // Test Case - not a section filter\n        m_filters.insert(m_filters.end(), filters.begin(), filters.end());\n    }\n}\nvoid SectionTracker::addNextFilters(std::vector<std::string> const &filters) {\n    if (filters.size() > 1)\n        m_filters.insert(m_filters.end(), filters.begin() + 1, filters.end());\n}\n\nstd::vector<std::string> const &SectionTracker::getFilters() const { return m_filters; }\n\nstd::string const &SectionTracker::trimmedName() const { return m_trimmed_name; }\n\n}  // namespace TestCaseTracking\n\nusing TestCaseTracking::ITracker;\nusing TestCaseTracking::SectionTracker;\nusing TestCaseTracking::TrackerContext;\n\n}  // namespace Catch\n\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n// end catch_test_case_tracker.cpp\n// start catch_test_registry.cpp\n\nnamespace Catch {\n\nauto makeTestInvoker(void (*testAsFunction)()) noexcept -> ITestInvoker * {\n    return new (std::nothrow) TestInvokerAsFunction(testAsFunction);\n}\n\nNameAndTags::NameAndTags(StringRef const &name_, StringRef const &tags_) noexcept\n        : name(name_), tags(tags_) {}\n\nAutoReg::AutoReg(ITestInvoker *invoker,\n                 SourceLineInfo const &lineInfo,\n                 StringRef const &classOrMethod,\n                 NameAndTags const &nameAndTags) noexcept {\n    CATCH_TRY {\n        getMutableRegistryHub().registerTest(\n                makeTestCase(invoker, extractClassName(classOrMethod), nameAndTags, lineInfo));\n    }\n    CATCH_CATCH_ALL {\n        // Do not throw when constructing global objects, instead register the\n        // exception to be processed later\n        getMutableRegistryHub().registerStartupException();\n    }\n}\n\nAutoReg::~AutoReg() = default;\n}  // namespace Catch\n// end catch_test_registry.cpp\n// start catch_test_spec.cpp\n\n#include <algorithm>\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\nTestSpec::Pattern::Pattern(std::string const &name) : m_name(name) {}\n\nTestSpec::Pattern::~Pattern() = default;\n\nstd::string const &TestSpec::Pattern::name() const { return m_name; }\n\nTestSpec::NamePattern::NamePattern(std::string const &name, std::string const &filterString)\n        : Pattern(filterString), m_wildcardPattern(toLower(name), CaseSensitive::No) {}\n\nbool TestSpec::NamePattern::matches(TestCaseInfo const &testCase) const {\n    return m_wildcardPattern.matches(testCase.name);\n}\n\nTestSpec::TagPattern::TagPattern(std::string const &tag, std::string const &filterString)\n        : Pattern(filterString), m_tag(toLower(tag)) {}\n\nbool TestSpec::TagPattern::matches(TestCaseInfo const &testCase) const {\n    return std::find(begin(testCase.lcaseTags), end(testCase.lcaseTags), m_tag) !=\n           end(testCase.lcaseTags);\n}\n\nTestSpec::ExcludedPattern::ExcludedPattern(PatternPtr const &underlyingPattern)\n        : Pattern(underlyingPattern->name()), m_underlyingPattern(underlyingPattern) {}\n\nbool TestSpec::ExcludedPattern::matches(TestCaseInfo const &testCase) const {\n    return !m_underlyingPattern->matches(testCase);\n}\n\nbool TestSpec::Filter::matches(TestCaseInfo const &testCase) const {\n    return std::all_of(m_patterns.begin(), m_patterns.end(),\n                       [&](PatternPtr const &p) { return p->matches(testCase); });\n}\n\nstd::string TestSpec::Filter::name() const {\n    std::string name;\n    for (auto const &p : m_patterns)\n        name += p->name();\n    return name;\n}\n\nbool TestSpec::hasFilters() const { return !m_filters.empty(); }\n\nbool TestSpec::matches(TestCaseInfo const &testCase) const {\n    return std::any_of(m_filters.begin(), m_filters.end(),\n                       [&](Filter const &f) { return f.matches(testCase); });\n}\n\nTestSpec::Matches TestSpec::matchesByFilter(std::vector<TestCase> const &testCases,\n                                            IConfig const &config) const {\n    Matches matches(m_filters.size());\n    std::transform(m_filters.begin(), m_filters.end(), matches.begin(), [&](Filter const &filter) {\n        std::vector<TestCase const *> currentMatches;\n        for (auto const &test : testCases)\n            if (isThrowSafe(test, config) && filter.matches(test))\n                currentMatches.emplace_back(&test);\n        return FilterMatch{filter.name(), currentMatches};\n    });\n    return matches;\n}\n\nconst TestSpec::vectorStrings &TestSpec::getInvalidArgs() const { return (m_invalidArgs); }\n\n}  // namespace Catch\n// end catch_test_spec.cpp\n// start catch_test_spec_parser.cpp\n\nnamespace Catch {\n\nTestSpecParser::TestSpecParser(ITagAliasRegistry const &tagAliases) : m_tagAliases(&tagAliases) {}\n\nTestSpecParser &TestSpecParser::parse(std::string const &arg) {\n    m_mode = None;\n    m_exclusion = false;\n    m_arg = m_tagAliases->expandAliases(arg);\n    m_escapeChars.clear();\n    m_substring.reserve(m_arg.size());\n    m_patternName.reserve(m_arg.size());\n    m_realPatternPos = 0;\n\n    for (m_pos = 0; m_pos < m_arg.size(); ++m_pos)\n        // if visitChar fails\n        if (!visitChar(m_arg[m_pos])) {\n            m_testSpec.m_invalidArgs.push_back(arg);\n            break;\n        }\n    endMode();\n    return *this;\n}\nTestSpec TestSpecParser::testSpec() {\n    addFilter();\n    return m_testSpec;\n}\nbool TestSpecParser::visitChar(char c) {\n    if ((m_mode != EscapedName) && (c == '\\\\')) {\n        escape();\n        addCharToPattern(c);\n        return true;\n    } else if ((m_mode != EscapedName) && (c == ',')) {\n        return separate();\n    }\n\n    switch (m_mode) {\n    case None:\n        if (processNoneChar(c))\n            return true;\n        break;\n    case Name:\n        processNameChar(c);\n        break;\n    case EscapedName:\n        endMode();\n        addCharToPattern(c);\n        return true;\n    default:\n    case Tag:\n    case QuotedName:\n        if (processOtherChar(c))\n            return true;\n        break;\n    }\n\n    m_substring += c;\n    if (!isControlChar(c)) {\n        m_patternName += c;\n        m_realPatternPos++;\n    }\n    return true;\n}\n// Two of the processing methods return true to signal the caller to return\n// without adding the given character to the current pattern strings\nbool TestSpecParser::processNoneChar(char c) {\n    switch (c) {\n    case ' ':\n        return true;\n    case '~':\n        m_exclusion = true;\n        return false;\n    case '[':\n        startNewMode(Tag);\n        return false;\n    case '\"':\n        startNewMode(QuotedName);\n        return false;\n    default:\n        startNewMode(Name);\n        return false;\n    }\n}\nvoid TestSpecParser::processNameChar(char c) {\n    if (c == '[') {\n        if (m_substring == \"exclude:\")\n            m_exclusion = true;\n        else\n            endMode();\n        startNewMode(Tag);\n    }\n}\nbool TestSpecParser::processOtherChar(char c) {\n    if (!isControlChar(c))\n        return false;\n    m_substring += c;\n    endMode();\n    return true;\n}\nvoid TestSpecParser::startNewMode(Mode mode) { m_mode = mode; }\nvoid TestSpecParser::endMode() {\n    switch (m_mode) {\n    case Name:\n    case QuotedName:\n        return addNamePattern();\n    case Tag:\n        return addTagPattern();\n    case EscapedName:\n        revertBackToLastMode();\n        return;\n    case None:\n    default:\n        return startNewMode(None);\n    }\n}\nvoid TestSpecParser::escape() {\n    saveLastMode();\n    m_mode = EscapedName;\n    m_escapeChars.push_back(m_realPatternPos);\n}\nbool TestSpecParser::isControlChar(char c) const {\n    switch (m_mode) {\n    default:\n        return false;\n    case None:\n        return c == '~';\n    case Name:\n        return c == '[';\n    case EscapedName:\n        return true;\n    case QuotedName:\n        return c == '\"';\n    case Tag:\n        return c == '[' || c == ']';\n    }\n}\n\nvoid TestSpecParser::addFilter() {\n    if (!m_currentFilter.m_patterns.empty()) {\n        m_testSpec.m_filters.push_back(m_currentFilter);\n        m_currentFilter = TestSpec::Filter();\n    }\n}\n\nvoid TestSpecParser::saveLastMode() { lastMode = m_mode; }\n\nvoid TestSpecParser::revertBackToLastMode() { m_mode = lastMode; }\n\nbool TestSpecParser::separate() {\n    if ((m_mode == QuotedName) || (m_mode == Tag)) {\n        // invalid argument, signal failure to previous scope.\n        m_mode = None;\n        m_pos = m_arg.size();\n        m_substring.clear();\n        m_patternName.clear();\n        m_realPatternPos = 0;\n        return false;\n    }\n    endMode();\n    addFilter();\n    return true;  // success\n}\n\nstd::string TestSpecParser::preprocessPattern() {\n    std::string token = m_patternName;\n    for (std::size_t i = 0; i < m_escapeChars.size(); ++i)\n        token = token.substr(0, m_escapeChars[i] - i) + token.substr(m_escapeChars[i] - i + 1);\n    m_escapeChars.clear();\n    if (startsWith(token, \"exclude:\")) {\n        m_exclusion = true;\n        token = token.substr(8);\n    }\n\n    m_patternName.clear();\n    m_realPatternPos = 0;\n\n    return token;\n}\n\nvoid TestSpecParser::addNamePattern() {\n    auto token = preprocessPattern();\n\n    if (!token.empty()) {\n        TestSpec::PatternPtr pattern = std::make_shared<TestSpec::NamePattern>(token, m_substring);\n        if (m_exclusion)\n            pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern);\n        m_currentFilter.m_patterns.push_back(pattern);\n    }\n    m_substring.clear();\n    m_exclusion = false;\n    m_mode = None;\n}\n\nvoid TestSpecParser::addTagPattern() {\n    auto token = preprocessPattern();\n\n    if (!token.empty()) {\n        // If the tag pattern is the \"hide and tag\" shorthand (e.g. [.foo])\n        // we have to create a separate hide tag and shorten the real one\n        if (token.size() > 1 && token[0] == '.') {\n            token.erase(token.begin());\n            TestSpec::PatternPtr pattern = std::make_shared<TestSpec::TagPattern>(\".\", m_substring);\n            if (m_exclusion) {\n                pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern);\n            }\n            m_currentFilter.m_patterns.push_back(pattern);\n        }\n\n        TestSpec::PatternPtr pattern = std::make_shared<TestSpec::TagPattern>(token, m_substring);\n\n        if (m_exclusion) {\n            pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern);\n        }\n        m_currentFilter.m_patterns.push_back(pattern);\n    }\n    m_substring.clear();\n    m_exclusion = false;\n    m_mode = None;\n}\n\nTestSpec parseTestSpec(std::string const &arg) {\n    return TestSpecParser(ITagAliasRegistry::get()).parse(arg).testSpec();\n}\n\n}  // namespace Catch\n// end catch_test_spec_parser.cpp\n// start catch_timer.cpp\n\n#include <chrono>\n\nstatic const uint64_t nanosecondsInSecond = 1000000000;\n\nnamespace Catch {\n\nauto getCurrentNanosecondsSinceEpoch() -> uint64_t {\n    return std::chrono::duration_cast<std::chrono::nanoseconds>(\n                   std::chrono::high_resolution_clock::now().time_since_epoch())\n            .count();\n}\n\nnamespace {\nauto estimateClockResolution() -> uint64_t {\n    uint64_t sum = 0;\n    static const uint64_t iterations = 1000000;\n\n    auto startTime = getCurrentNanosecondsSinceEpoch();\n\n    for (std::size_t i = 0; i < iterations; ++i) {\n        uint64_t ticks;\n        uint64_t baseTicks = getCurrentNanosecondsSinceEpoch();\n        do {\n            ticks = getCurrentNanosecondsSinceEpoch();\n        } while (ticks == baseTicks);\n\n        auto delta = ticks - baseTicks;\n        sum += delta;\n\n        // If we have been calibrating for over 3 seconds -- the clock\n        // is terrible and we should move on.\n        // TBD: How to signal that the measured resolution is probably wrong?\n        if (ticks > startTime + 3 * nanosecondsInSecond) {\n            return sum / (i + 1u);\n        }\n    }\n\n    // We're just taking the mean, here. To do better we could take the std. dev\n    // and exclude outliers\n    // - and potentially do more iterations if there's a high variance.\n    return sum / iterations;\n}\n}  // namespace\nauto getEstimatedClockResolution() -> uint64_t {\n    static auto s_resolution = estimateClockResolution();\n    return s_resolution;\n}\n\nvoid Timer::start() { m_nanoseconds = getCurrentNanosecondsSinceEpoch(); }\nauto Timer::getElapsedNanoseconds() const -> uint64_t {\n    return getCurrentNanosecondsSinceEpoch() - m_nanoseconds;\n}\nauto Timer::getElapsedMicroseconds() const -> uint64_t { return getElapsedNanoseconds() / 1000; }\nauto Timer::getElapsedMilliseconds() const -> unsigned int {\n    return static_cast<unsigned int>(getElapsedMicroseconds() / 1000);\n}\nauto Timer::getElapsedSeconds() const -> double { return getElapsedMicroseconds() / 1000000.0; }\n\n}  // namespace Catch\n// end catch_timer.cpp\n// start catch_tostring.cpp\n\n#if defined(__clang__)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n#pragma clang diagnostic ignored \"-Wglobal-constructors\"\n#endif\n\n// Enable specific decls locally\n#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)\n#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER\n#endif\n\n#include <cmath>\n#include <iomanip>\n\nnamespace Catch {\n\nnamespace Detail {\n\nconst std::string unprintableString = \"{?}\";\n\nnamespace {\nconst int hexThreshold = 255;\n\nstruct Endianness {\n    enum Arch { Big, Little };\n\n    static Arch which() {\n        int one = 1;\n        // If the lowest byte we read is non-zero, we can assume\n        // that little endian format is used.\n        auto value = *reinterpret_cast<char *>(&one);\n        return value ? Little : Big;\n    }\n};\n}  // namespace\n\nstd::string rawMemoryToString(const void *object, std::size_t size) {\n    // Reverse order for little endian architectures\n    int i = 0, end = static_cast<int>(size), inc = 1;\n    if (Endianness::which() == Endianness::Little) {\n        i = end - 1;\n        end = inc = -1;\n    }\n\n    unsigned char const *bytes = static_cast<unsigned char const *>(object);\n    ReusableStringStream rss;\n    rss << \"0x\" << std::setfill('0') << std::hex;\n    for (; i != end; i += inc)\n        rss << std::setw(2) << static_cast<unsigned>(bytes[i]);\n    return rss.str();\n}\n}  // namespace Detail\n\ntemplate <typename T>\nstd::string fpToString(T value, int precision) {\n    if (Catch::isnan(value)) {\n        return \"nan\";\n    }\n\n    ReusableStringStream rss;\n    rss << std::setprecision(precision) << std::fixed << value;\n    std::string d = rss.str();\n    std::size_t i = d.find_last_not_of('0');\n    if (i != std::string::npos && i != d.size() - 1) {\n        if (d[i] == '.')\n            i++;\n        d = d.substr(0, i + 1);\n    }\n    return d;\n}\n\n//// ======================================================= ////\n//\n//   Out-of-line defs for full specialization of StringMaker\n//\n//// ======================================================= ////\n\nstd::string StringMaker<std::string>::convert(const std::string &str) {\n    if (!getCurrentContext().getConfig()->showInvisibles()) {\n        return '\"' + str + '\"';\n    }\n\n    std::string s(\"\\\"\");\n    for (char c : str) {\n        switch (c) {\n        case '\\n':\n            s.append(\"\\\\n\");\n            break;\n        case '\\t':\n            s.append(\"\\\\t\");\n            break;\n        default:\n            s.push_back(c);\n            break;\n        }\n    }\n    s.append(\"\\\"\");\n    return s;\n}\n\n#ifdef CATCH_CONFIG_CPP17_STRING_VIEW\nstd::string StringMaker<std::string_view>::convert(std::string_view str) {\n    return ::Catch::Detail::stringify(std::string{str});\n}\n#endif\n\nstd::string StringMaker<char const *>::convert(char const *str) {\n    if (str) {\n        return ::Catch::Detail::stringify(std::string{str});\n    } else {\n        return {\"{null string}\"};\n    }\n}\nstd::string StringMaker<char *>::convert(char *str) {\n    if (str) {\n        return ::Catch::Detail::stringify(std::string{str});\n    } else {\n        return {\"{null string}\"};\n    }\n}\n\n#ifdef CATCH_CONFIG_WCHAR\nstd::string StringMaker<std::wstring>::convert(const std::wstring &wstr) {\n    std::string s;\n    s.reserve(wstr.size());\n    for (auto c : wstr) {\n        s += (c <= 0xff) ? static_cast<char>(c) : '?';\n    }\n    return ::Catch::Detail::stringify(s);\n}\n\n#ifdef CATCH_CONFIG_CPP17_STRING_VIEW\nstd::string StringMaker<std::wstring_view>::convert(std::wstring_view str) {\n    return StringMaker<std::wstring>::convert(std::wstring(str));\n}\n#endif\n\nstd::string StringMaker<wchar_t const *>::convert(wchar_t const *str) {\n    if (str) {\n        return ::Catch::Detail::stringify(std::wstring{str});\n    } else {\n        return {\"{null string}\"};\n    }\n}\nstd::string StringMaker<wchar_t *>::convert(wchar_t *str) {\n    if (str) {\n        return ::Catch::Detail::stringify(std::wstring{str});\n    } else {\n        return {\"{null string}\"};\n    }\n}\n#endif\n\n#if defined(CATCH_CONFIG_CPP17_BYTE)\n#include <cstddef>\nstd::string StringMaker<std::byte>::convert(std::byte value) {\n    return ::Catch::Detail::stringify(std::to_integer<unsigned long long>(value));\n}\n#endif  // defined(CATCH_CONFIG_CPP17_BYTE)\n\nstd::string StringMaker<int>::convert(int value) {\n    return ::Catch::Detail::stringify(static_cast<long long>(value));\n}\nstd::string StringMaker<long>::convert(long value) {\n    return ::Catch::Detail::stringify(static_cast<long long>(value));\n}\nstd::string StringMaker<long long>::convert(long long value) {\n    ReusableStringStream rss;\n    rss << value;\n    if (value > Detail::hexThreshold) {\n        rss << \" (0x\" << std::hex << value << ')';\n    }\n    return rss.str();\n}\n\nstd::string StringMaker<unsigned int>::convert(unsigned int value) {\n    return ::Catch::Detail::stringify(static_cast<unsigned long long>(value));\n}\nstd::string StringMaker<unsigned long>::convert(unsigned long value) {\n    return ::Catch::Detail::stringify(static_cast<unsigned long long>(value));\n}\nstd::string StringMaker<unsigned long long>::convert(unsigned long long value) {\n    ReusableStringStream rss;\n    rss << value;\n    if (value > Detail::hexThreshold) {\n        rss << \" (0x\" << std::hex << value << ')';\n    }\n    return rss.str();\n}\n\nstd::string StringMaker<bool>::convert(bool b) { return b ? \"true\" : \"false\"; }\n\nstd::string StringMaker<signed char>::convert(signed char value) {\n    if (value == '\\r') {\n        return \"'\\\\r'\";\n    } else if (value == '\\f') {\n        return \"'\\\\f'\";\n    } else if (value == '\\n') {\n        return \"'\\\\n'\";\n    } else if (value == '\\t') {\n        return \"'\\\\t'\";\n    } else if ('\\0' <= value && value < ' ') {\n        return ::Catch::Detail::stringify(static_cast<unsigned int>(value));\n    } else {\n        char chstr[] = \"' '\";\n        chstr[1] = value;\n        return chstr;\n    }\n}\nstd::string StringMaker<char>::convert(char c) {\n    return ::Catch::Detail::stringify(static_cast<signed char>(c));\n}\nstd::string StringMaker<unsigned char>::convert(unsigned char c) {\n    return ::Catch::Detail::stringify(static_cast<char>(c));\n}\n\nstd::string StringMaker<std::nullptr_t>::convert(std::nullptr_t) { return \"nullptr\"; }\n\nint StringMaker<float>::precision = 5;\n\nstd::string StringMaker<float>::convert(float value) { return fpToString(value, precision) + 'f'; }\n\nint StringMaker<double>::precision = 10;\n\nstd::string StringMaker<double>::convert(double value) { return fpToString(value, precision); }\n\nstd::string ratio_string<std::atto>::symbol() { return \"a\"; }\nstd::string ratio_string<std::femto>::symbol() { return \"f\"; }\nstd::string ratio_string<std::pico>::symbol() { return \"p\"; }\nstd::string ratio_string<std::nano>::symbol() { return \"n\"; }\nstd::string ratio_string<std::micro>::symbol() { return \"u\"; }\nstd::string ratio_string<std::milli>::symbol() { return \"m\"; }\n\n}  // end namespace Catch\n\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n\n// end catch_tostring.cpp\n// start catch_totals.cpp\n\nnamespace Catch {\n\nCounts Counts::operator-(Counts const &other) const {\n    Counts diff;\n    diff.passed = passed - other.passed;\n    diff.failed = failed - other.failed;\n    diff.failedButOk = failedButOk - other.failedButOk;\n    return diff;\n}\n\nCounts &Counts::operator+=(Counts const &other) {\n    passed += other.passed;\n    failed += other.failed;\n    failedButOk += other.failedButOk;\n    return *this;\n}\n\nstd::size_t Counts::total() const { return passed + failed + failedButOk; }\nbool Counts::allPassed() const { return failed == 0 && failedButOk == 0; }\nbool Counts::allOk() const { return failed == 0; }\n\nTotals Totals::operator-(Totals const &other) const {\n    Totals diff;\n    diff.assertions = assertions - other.assertions;\n    diff.testCases = testCases - other.testCases;\n    return diff;\n}\n\nTotals &Totals::operator+=(Totals const &other) {\n    assertions += other.assertions;\n    testCases += other.testCases;\n    return *this;\n}\n\nTotals Totals::delta(Totals const &prevTotals) const {\n    Totals diff = *this - prevTotals;\n    if (diff.assertions.failed > 0)\n        ++diff.testCases.failed;\n    else if (diff.assertions.failedButOk > 0)\n        ++diff.testCases.failedButOk;\n    else\n        ++diff.testCases.passed;\n    return diff;\n}\n\n}  // namespace Catch\n// end catch_totals.cpp\n// start catch_uncaught_exceptions.cpp\n\n// start catch_config_uncaught_exceptions.hpp\n\n//              Copyright Catch2 Authors\n// Distributed under the Boost Software License, Version 1.0.\n//   (See accompanying file LICENSE_1_0.txt or copy at\n//        https://www.boost.org/LICENSE_1_0.txt)\n\n// SPDX-License-Identifier: BSL-1.0\n\n#ifndef CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP\n#define CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP\n\n#if defined(_MSC_VER)\n#if _MSC_VER >= 1900  // Visual Studio 2015 or newer\n#define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS\n#endif\n#endif\n\n#include <exception>\n\n#if defined(__cpp_lib_uncaught_exceptions) && \\\n        !defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)\n\n#define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS\n#endif  // __cpp_lib_uncaught_exceptions\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && \\\n        !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) &&  \\\n        !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)\n\n#define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS\n#endif\n\n#endif  // CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP\n// end catch_config_uncaught_exceptions.hpp\n#include <exception>\n\nnamespace Catch {\nbool uncaught_exceptions() {\n#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n    return false;\n#elif defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)\n    return std::uncaught_exceptions() > 0;\n#else\n    return std::uncaught_exception();\n#endif\n}\n}  // end namespace Catch\n// end catch_uncaught_exceptions.cpp\n// start catch_version.cpp\n\n#include <ostream>\n\nnamespace Catch {\n\nVersion::Version(unsigned int _majorVersion,\n                 unsigned int _minorVersion,\n                 unsigned int _patchNumber,\n                 char const *const _branchName,\n                 unsigned int _buildNumber)\n        : majorVersion(_majorVersion),\n          minorVersion(_minorVersion),\n          patchNumber(_patchNumber),\n          branchName(_branchName),\n          buildNumber(_buildNumber) {}\n\nstd::ostream &operator<<(std::ostream &os, Version const &version) {\n    os << version.majorVersion << '.' << version.minorVersion << '.' << version.patchNumber;\n    // branchName is never null -> 0th char is \\0 if it is empty\n    if (version.branchName[0]) {\n        os << '-' << version.branchName << '.' << version.buildNumber;\n    }\n    return os;\n}\n\nVersion const &libraryVersion() {\n    static Version version(2, 13, 7, \"\", 0);\n    return version;\n}\n\n}  // namespace Catch\n// end catch_version.cpp\n// start catch_wildcard_pattern.cpp\n\nnamespace Catch {\n\nWildcardPattern::WildcardPattern(std::string const &pattern, CaseSensitive::Choice caseSensitivity)\n        : m_caseSensitivity(caseSensitivity), m_pattern(normaliseString(pattern)) {\n    if (startsWith(m_pattern, '*')) {\n        m_pattern = m_pattern.substr(1);\n        m_wildcard = WildcardAtStart;\n    }\n    if (endsWith(m_pattern, '*')) {\n        m_pattern = m_pattern.substr(0, m_pattern.size() - 1);\n        m_wildcard = static_cast<WildcardPosition>(m_wildcard | WildcardAtEnd);\n    }\n}\n\nbool WildcardPattern::matches(std::string const &str) const {\n    switch (m_wildcard) {\n    case NoWildcard:\n        return m_pattern == normaliseString(str);\n    case WildcardAtStart:\n        return endsWith(normaliseString(str), m_pattern);\n    case WildcardAtEnd:\n        return startsWith(normaliseString(str), m_pattern);\n    case WildcardAtBothEnds:\n        return contains(normaliseString(str), m_pattern);\n    default:\n        CATCH_INTERNAL_ERROR(\"Unknown enum\");\n    }\n}\n\nstd::string WildcardPattern::normaliseString(std::string const &str) const {\n    return trim(m_caseSensitivity == CaseSensitive::No ? toLower(str) : str);\n}\n}  // namespace Catch\n// end catch_wildcard_pattern.cpp\n// start catch_xmlwriter.cpp\n\n#include <iomanip>\n#include <type_traits>\n\nnamespace Catch {\n\nnamespace {\n\nsize_t trailingBytes(unsigned char c) {\n    if ((c & 0xE0) == 0xC0) {\n        return 2;\n    }\n    if ((c & 0xF0) == 0xE0) {\n        return 3;\n    }\n    if ((c & 0xF8) == 0xF0) {\n        return 4;\n    }\n    CATCH_INTERNAL_ERROR(\"Invalid multibyte utf-8 start byte encountered\");\n}\n\nuint32_t headerValue(unsigned char c) {\n    if ((c & 0xE0) == 0xC0) {\n        return c & 0x1F;\n    }\n    if ((c & 0xF0) == 0xE0) {\n        return c & 0x0F;\n    }\n    if ((c & 0xF8) == 0xF0) {\n        return c & 0x07;\n    }\n    CATCH_INTERNAL_ERROR(\"Invalid multibyte utf-8 start byte encountered\");\n}\n\nvoid hexEscapeChar(std::ostream &os, unsigned char c) {\n    std::ios_base::fmtflags f(os.flags());\n    os << \"\\\\x\" << std::uppercase << std::hex << std::setfill('0') << std::setw(2)\n       << static_cast<int>(c);\n    os.flags(f);\n}\n\nbool shouldNewline(XmlFormatting fmt) {\n    return !!(static_cast<std::underlying_type<XmlFormatting>::type>(fmt & XmlFormatting::Newline));\n}\n\nbool shouldIndent(XmlFormatting fmt) {\n    return !!(static_cast<std::underlying_type<XmlFormatting>::type>(fmt & XmlFormatting::Indent));\n}\n\n}  // anonymous namespace\n\nXmlFormatting operator|(XmlFormatting lhs, XmlFormatting rhs) {\n    return static_cast<XmlFormatting>(static_cast<std::underlying_type<XmlFormatting>::type>(lhs) |\n                                      static_cast<std::underlying_type<XmlFormatting>::type>(rhs));\n}\n\nXmlFormatting operator&(XmlFormatting lhs, XmlFormatting rhs) {\n    return static_cast<XmlFormatting>(static_cast<std::underlying_type<XmlFormatting>::type>(lhs) &\n                                      static_cast<std::underlying_type<XmlFormatting>::type>(rhs));\n}\n\nXmlEncode::XmlEncode(std::string const &str, ForWhat forWhat) : m_str(str), m_forWhat(forWhat) {}\n\nvoid XmlEncode::encodeTo(std::ostream &os) const {\n    // Apostrophe escaping not necessary if we always use \" to write attributes\n    // (see: http://www.w3.org/TR/xml/#syntax)\n\n    for (std::size_t idx = 0; idx < m_str.size(); ++idx) {\n        unsigned char c = m_str[idx];\n        switch (c) {\n        case '<':\n            os << \"&lt;\";\n            break;\n        case '&':\n            os << \"&amp;\";\n            break;\n\n        case '>':\n            // See: http://www.w3.org/TR/xml/#syntax\n            if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']')\n                os << \"&gt;\";\n            else\n                os << c;\n            break;\n\n        case '\\\"':\n            if (m_forWhat == ForAttributes)\n                os << \"&quot;\";\n            else\n                os << c;\n            break;\n\n        default:\n            // Check for control characters and invalid utf-8\n\n            // Escape control characters in standard ascii\n            // see\n            // http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0\n            if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {\n                hexEscapeChar(os, c);\n                break;\n            }\n\n            // Plain ASCII: Write it to stream\n            if (c < 0x7F) {\n                os << c;\n                break;\n            }\n\n            // UTF-8 territory\n            // Check if the encoding is valid and if it is not, hex escape bytes.\n            // Important: We do not check the exact decoded values for validity, only\n            // the encoding format First check that this bytes is a valid lead byte:\n            // This means that it is not encoded as 1111 1XXX\n            // Or as 10XX XXXX\n            if (c < 0xC0 || c >= 0xF8) {\n                hexEscapeChar(os, c);\n                break;\n            }\n\n            auto encBytes = trailingBytes(c);\n            // Are there enough bytes left to avoid accessing out-of-bounds memory?\n            if (idx + encBytes - 1 >= m_str.size()) {\n                hexEscapeChar(os, c);\n                break;\n            }\n            // The header is valid, check data\n            // The next encBytes bytes must together be a valid utf-8\n            // This means: bitpattern 10XX XXXX and the extracted value is sane (ish)\n            bool valid = true;\n            uint32_t value = headerValue(c);\n            for (std::size_t n = 1; n < encBytes; ++n) {\n                unsigned char nc = m_str[idx + n];\n                valid &= ((nc & 0xC0) == 0x80);\n                value = (value << 6) | (nc & 0x3F);\n            }\n\n            if (\n                    // Wrong bit pattern of following bytes\n                    (!valid) ||\n                    // Overlong encodings\n                    (value < 0x80) || (0x80 <= value && value < 0x800 && encBytes > 2) ||\n                    (0x800 < value && value < 0x10000 && encBytes > 3) ||\n                    // Encoded value out of range\n                    (value >= 0x110000)) {\n                hexEscapeChar(os, c);\n                break;\n            }\n\n            // If we got here, this is in fact a valid(ish) utf-8 sequence\n            for (std::size_t n = 0; n < encBytes; ++n) {\n                os << m_str[idx + n];\n            }\n            idx += encBytes - 1;\n            break;\n        }\n    }\n}\n\nstd::ostream &operator<<(std::ostream &os, XmlEncode const &xmlEncode) {\n    xmlEncode.encodeTo(os);\n    return os;\n}\n\nXmlWriter::ScopedElement::ScopedElement(XmlWriter *writer, XmlFormatting fmt)\n        : m_writer(writer), m_fmt(fmt) {}\n\nXmlWriter::ScopedElement::ScopedElement(ScopedElement &&other) noexcept\n        : m_writer(other.m_writer), m_fmt(other.m_fmt) {\n    other.m_writer = nullptr;\n    other.m_fmt = XmlFormatting::None;\n}\nXmlWriter::ScopedElement &XmlWriter::ScopedElement::operator=(ScopedElement &&other) noexcept {\n    if (m_writer) {\n        m_writer->endElement();\n    }\n    m_writer = other.m_writer;\n    other.m_writer = nullptr;\n    m_fmt = other.m_fmt;\n    other.m_fmt = XmlFormatting::None;\n    return *this;\n}\n\nXmlWriter::ScopedElement::~ScopedElement() {\n    if (m_writer) {\n        m_writer->endElement(m_fmt);\n    }\n}\n\nXmlWriter::ScopedElement &XmlWriter::ScopedElement::writeText(std::string const &text,\n                                                              XmlFormatting fmt) {\n    m_writer->writeText(text, fmt);\n    return *this;\n}\n\nXmlWriter::XmlWriter(std::ostream &os) : m_os(os) { writeDeclaration(); }\n\nXmlWriter::~XmlWriter() {\n    while (!m_tags.empty()) {\n        endElement();\n    }\n    newlineIfNecessary();\n}\n\nXmlWriter &XmlWriter::startElement(std::string const &name, XmlFormatting fmt) {\n    ensureTagClosed();\n    newlineIfNecessary();\n    if (shouldIndent(fmt)) {\n        m_os << m_indent;\n        m_indent += \"  \";\n    }\n    m_os << '<' << name;\n    m_tags.push_back(name);\n    m_tagIsOpen = true;\n    applyFormatting(fmt);\n    return *this;\n}\n\nXmlWriter::ScopedElement XmlWriter::scopedElement(std::string const &name, XmlFormatting fmt) {\n    ScopedElement scoped(this, fmt);\n    startElement(name, fmt);\n    return scoped;\n}\n\nXmlWriter &XmlWriter::endElement(XmlFormatting fmt) {\n    m_indent = m_indent.substr(0, m_indent.size() - 2);\n\n    if (m_tagIsOpen) {\n        m_os << \"/>\";\n        m_tagIsOpen = false;\n    } else {\n        newlineIfNecessary();\n        if (shouldIndent(fmt)) {\n            m_os << m_indent;\n        }\n        m_os << \"</\" << m_tags.back() << \">\";\n    }\n    m_os << std::flush;\n    applyFormatting(fmt);\n    m_tags.pop_back();\n    return *this;\n}\n\nXmlWriter &XmlWriter::writeAttribute(std::string const &name, std::string const &attribute) {\n    if (!name.empty() && !attribute.empty())\n        m_os << ' ' << name << \"=\\\"\" << XmlEncode(attribute, XmlEncode::ForAttributes) << '\"';\n    return *this;\n}\n\nXmlWriter &XmlWriter::writeAttribute(std::string const &name, bool attribute) {\n    m_os << ' ' << name << \"=\\\"\" << (attribute ? \"true\" : \"false\") << '\"';\n    return *this;\n}\n\nXmlWriter &XmlWriter::writeText(std::string const &text, XmlFormatting fmt) {\n    if (!text.empty()) {\n        bool tagWasOpen = m_tagIsOpen;\n        ensureTagClosed();\n        if (tagWasOpen && shouldIndent(fmt)) {\n            m_os << m_indent;\n        }\n        m_os << XmlEncode(text);\n        applyFormatting(fmt);\n    }\n    return *this;\n}\n\nXmlWriter &XmlWriter::writeComment(std::string const &text, XmlFormatting fmt) {\n    ensureTagClosed();\n    if (shouldIndent(fmt)) {\n        m_os << m_indent;\n    }\n    m_os << \"<!--\" << text << \"-->\";\n    applyFormatting(fmt);\n    return *this;\n}\n\nvoid XmlWriter::writeStylesheetRef(std::string const &url) {\n    m_os << \"<?xml-stylesheet type=\\\"text/xsl\\\" href=\\\"\" << url << \"\\\"?>\\n\";\n}\n\nXmlWriter &XmlWriter::writeBlankLine() {\n    ensureTagClosed();\n    m_os << '\\n';\n    return *this;\n}\n\nvoid XmlWriter::ensureTagClosed() {\n    if (m_tagIsOpen) {\n        m_os << '>' << std::flush;\n        newlineIfNecessary();\n        m_tagIsOpen = false;\n    }\n}\n\nvoid XmlWriter::applyFormatting(XmlFormatting fmt) { m_needsNewline = shouldNewline(fmt); }\n\nvoid XmlWriter::writeDeclaration() { m_os << \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n\"; }\n\nvoid XmlWriter::newlineIfNecessary() {\n    if (m_needsNewline) {\n        m_os << std::endl;\n        m_needsNewline = false;\n    }\n}\n}  // namespace Catch\n// end catch_xmlwriter.cpp\n// start catch_reporter_bases.cpp\n\n#include <cassert>\n#include <cfloat>\n#include <cstdio>\n#include <cstring>\n#include <memory>\n\nnamespace Catch {\nvoid prepareExpandedExpression(AssertionResult &result) { result.getExpandedExpression(); }\n\n// Because formatting using c++ streams is stateful, drop down to C is required\n// Alternatively we could use stringstream, but its performance is... not good.\nstd::string getFormattedDuration(double duration) {\n    // Max exponent + 1 is required to represent the whole part\n    // + 1 for decimal point\n    // + 3 for the 3 decimal places\n    // + 1 for null terminator\n    const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1;\n    char buffer[maxDoubleSize];\n\n    // Save previous errno, to prevent sprintf from overwriting it\n    ErrnoGuard guard;\n#ifdef _MSC_VER\n    sprintf_s(buffer, \"%.3f\", duration);\n#else\n    std::sprintf(buffer, \"%.3f\", duration);\n#endif\n    return std::string(buffer);\n}\n\nbool shouldShowDuration(IConfig const &config, double duration) {\n    if (config.showDurations() == ShowDurations::Always) {\n        return true;\n    }\n    if (config.showDurations() == ShowDurations::Never) {\n        return false;\n    }\n    const double min = config.minDuration();\n    return min >= 0 && duration >= min;\n}\n\nstd::string serializeFilters(std::vector<std::string> const &container) {\n    ReusableStringStream oss;\n    bool first = true;\n    for (auto &&filter : container) {\n        if (!first)\n            oss << ' ';\n        else\n            first = false;\n\n        oss << filter;\n    }\n    return oss.str();\n}\n\nTestEventListenerBase::TestEventListenerBase(ReporterConfig const &_config)\n        : StreamingReporterBase(_config) {}\n\nstd::set<Verbosity> TestEventListenerBase::getSupportedVerbosities() {\n    return {Verbosity::Quiet, Verbosity::Normal, Verbosity::High};\n}\n\nvoid TestEventListenerBase::assertionStarting(AssertionInfo const &) {}\n\nbool TestEventListenerBase::assertionEnded(AssertionStats const &) { return false; }\n\n}  // end namespace Catch\n// end catch_reporter_bases.cpp\n// start catch_reporter_compact.cpp\n\nnamespace {\n\n#ifdef CATCH_PLATFORM_MAC\nconst char *failedString() { return \"FAILED\"; }\nconst char *passedString() { return \"PASSED\"; }\n#else\nconst char *failedString() { return \"failed\"; }\nconst char *passedString() { return \"passed\"; }\n#endif\n\n// Colour::LightGrey\nCatch::Colour::Code dimColour() { return Catch::Colour::FileName; }\n\nstd::string bothOrAll(std::size_t count) {\n    return count == 1 ? std::string() : count == 2 ? \"both \" : \"all \";\n}\n\n}  // namespace\n\nnamespace Catch {\nnamespace {\n// Colour, message variants:\n// - white: No tests ran.\n// -   red: Failed [both/all] N test cases, failed [both/all] M assertions.\n// - white: Passed [both/all] N test cases (no assertions).\n// -   red: Failed N tests cases, failed M assertions.\n// - green: Passed [both/all] N tests cases with M assertions.\nvoid printTotals(std::ostream &out, const Totals &totals) {\n    if (totals.testCases.total() == 0) {\n        out << \"No tests ran.\";\n    } else if (totals.testCases.failed == totals.testCases.total()) {\n        Colour colour(Colour::ResultError);\n        const std::string qualify_assertions_failed =\n                totals.assertions.failed == totals.assertions.total()\n                        ? bothOrAll(totals.assertions.failed)\n                        : std::string();\n        out << \"Failed \" << bothOrAll(totals.testCases.failed)\n            << pluralise(totals.testCases.failed, \"test case\")\n            << \", \"\n               \"failed \"\n            << qualify_assertions_failed << pluralise(totals.assertions.failed, \"assertion\") << '.';\n    } else if (totals.assertions.total() == 0) {\n        out << \"Passed \" << bothOrAll(totals.testCases.total())\n            << pluralise(totals.testCases.total(), \"test case\") << \" (no assertions).\";\n    } else if (totals.assertions.failed) {\n        Colour colour(Colour::ResultError);\n        out << \"Failed \" << pluralise(totals.testCases.failed, \"test case\")\n            << \", \"\n               \"failed \"\n            << pluralise(totals.assertions.failed, \"assertion\") << '.';\n    } else {\n        Colour colour(Colour::ResultSuccess);\n        out << \"Passed \" << bothOrAll(totals.testCases.passed)\n            << pluralise(totals.testCases.passed, \"test case\") << \" with \"\n            << pluralise(totals.assertions.passed, \"assertion\") << '.';\n    }\n}\n\n// Implementation of CompactReporter formatting\nclass AssertionPrinter {\npublic:\n    AssertionPrinter &operator=(AssertionPrinter const &) = delete;\n    AssertionPrinter(AssertionPrinter const &) = delete;\n    AssertionPrinter(std::ostream &_stream, AssertionStats const &_stats, bool _printInfoMessages)\n            : stream(_stream),\n              result(_stats.assertionResult),\n              messages(_stats.infoMessages),\n              itMessage(_stats.infoMessages.begin()),\n              printInfoMessages(_printInfoMessages) {}\n\n    void print() {\n        printSourceInfo();\n\n        itMessage = messages.begin();\n\n        switch (result.getResultType()) {\n        case ResultWas::Ok:\n            printResultType(Colour::ResultSuccess, passedString());\n            printOriginalExpression();\n            printReconstructedExpression();\n            if (!result.hasExpression())\n                printRemainingMessages(Colour::None);\n            else\n                printRemainingMessages();\n            break;\n        case ResultWas::ExpressionFailed:\n            if (result.isOk())\n                printResultType(Colour::ResultSuccess,\n                                failedString() + std::string(\" - but was ok\"));\n            else\n                printResultType(Colour::Error, failedString());\n            printOriginalExpression();\n            printReconstructedExpression();\n            printRemainingMessages();\n            break;\n        case ResultWas::ThrewException:\n            printResultType(Colour::Error, failedString());\n            printIssue(\"unexpected exception with message:\");\n            printMessage();\n            printExpressionWas();\n            printRemainingMessages();\n            break;\n        case ResultWas::FatalErrorCondition:\n            printResultType(Colour::Error, failedString());\n            printIssue(\"fatal error condition with message:\");\n            printMessage();\n            printExpressionWas();\n            printRemainingMessages();\n            break;\n        case ResultWas::DidntThrowException:\n            printResultType(Colour::Error, failedString());\n            printIssue(\"expected exception, got none\");\n            printExpressionWas();\n            printRemainingMessages();\n            break;\n        case ResultWas::Info:\n            printResultType(Colour::None, \"info\");\n            printMessage();\n            printRemainingMessages();\n            break;\n        case ResultWas::Warning:\n            printResultType(Colour::None, \"warning\");\n            printMessage();\n            printRemainingMessages();\n            break;\n        case ResultWas::ExplicitFailure:\n            printResultType(Colour::Error, failedString());\n            printIssue(\"explicitly\");\n            printRemainingMessages(Colour::None);\n            break;\n            // These cases are here to prevent compiler warnings\n        case ResultWas::Unknown:\n        case ResultWas::FailureBit:\n        case ResultWas::Exception:\n            printResultType(Colour::Error, \"** internal error **\");\n            break;\n        }\n    }\n\nprivate:\n    void printSourceInfo() const {\n        Colour colourGuard(Colour::FileName);\n        stream << result.getSourceInfo() << ':';\n    }\n\n    void printResultType(Colour::Code colour, std::string const &passOrFail) const {\n        if (!passOrFail.empty()) {\n            {\n                Colour colourGuard(colour);\n                stream << ' ' << passOrFail;\n            }\n            stream << ':';\n        }\n    }\n\n    void printIssue(std::string const &issue) const { stream << ' ' << issue; }\n\n    void printExpressionWas() {\n        if (result.hasExpression()) {\n            stream << ';';\n            {\n                Colour colour(dimColour());\n                stream << \" expression was:\";\n            }\n            printOriginalExpression();\n        }\n    }\n\n    void printOriginalExpression() const {\n        if (result.hasExpression()) {\n            stream << ' ' << result.getExpression();\n        }\n    }\n\n    void printReconstructedExpression() const {\n        if (result.hasExpandedExpression()) {\n            {\n                Colour colour(dimColour());\n                stream << \" for: \";\n            }\n            stream << result.getExpandedExpression();\n        }\n    }\n\n    void printMessage() {\n        if (itMessage != messages.end()) {\n            stream << \" '\" << itMessage->message << '\\'';\n            ++itMessage;\n        }\n    }\n\n    void printRemainingMessages(Colour::Code colour = dimColour()) {\n        if (itMessage == messages.end())\n            return;\n\n        const auto itEnd = messages.cend();\n        const auto N = static_cast<std::size_t>(std::distance(itMessage, itEnd));\n\n        {\n            Colour colourGuard(colour);\n            stream << \" with \" << pluralise(N, \"message\") << ':';\n        }\n\n        while (itMessage != itEnd) {\n            // If this assertion is a warning ignore any INFO messages\n            if (printInfoMessages || itMessage->type != ResultWas::Info) {\n                printMessage();\n                if (itMessage != itEnd) {\n                    Colour colourGuard(dimColour());\n                    stream << \" and\";\n                }\n                continue;\n            }\n            ++itMessage;\n        }\n    }\n\nprivate:\n    std::ostream &stream;\n    AssertionResult const &result;\n    std::vector<MessageInfo> messages;\n    std::vector<MessageInfo>::const_iterator itMessage;\n    bool printInfoMessages;\n};\n\n}  // namespace\n\nstd::string CompactReporter::getDescription() {\n    return \"Reports test results on a single line, suitable for IDEs\";\n}\n\nvoid CompactReporter::noMatchingTestCases(std::string const &spec) {\n    stream << \"No test cases matched '\" << spec << '\\'' << std::endl;\n}\n\nvoid CompactReporter::assertionStarting(AssertionInfo const &) {}\n\nbool CompactReporter::assertionEnded(AssertionStats const &_assertionStats) {\n    AssertionResult const &result = _assertionStats.assertionResult;\n\n    bool printInfoMessages = true;\n\n    // Drop out if result was successful and we're not printing those\n    if (!m_config->includeSuccessfulResults() && result.isOk()) {\n        if (result.getResultType() != ResultWas::Warning)\n            return false;\n        printInfoMessages = false;\n    }\n\n    AssertionPrinter printer(stream, _assertionStats, printInfoMessages);\n    printer.print();\n\n    stream << std::endl;\n    return true;\n}\n\nvoid CompactReporter::sectionEnded(SectionStats const &_sectionStats) {\n    double dur = _sectionStats.durationInSeconds;\n    if (shouldShowDuration(*m_config, dur)) {\n        stream << getFormattedDuration(dur) << \" s: \" << _sectionStats.sectionInfo.name\n               << std::endl;\n    }\n}\n\nvoid CompactReporter::testRunEnded(TestRunStats const &_testRunStats) {\n    printTotals(stream, _testRunStats.totals);\n    stream << '\\n' << std::endl;\n    StreamingReporterBase::testRunEnded(_testRunStats);\n}\n\nCompactReporter::~CompactReporter() {}\n\nCATCH_REGISTER_REPORTER(\"compact\", CompactReporter)\n\n}  // end namespace Catch\n// end catch_reporter_compact.cpp\n// start catch_reporter_console.cpp\n\n#include <cfloat>\n#include <cstdio>\n\n#if defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable : 4061)  // Not all labels are EXPLICITLY handled in switch\n// Note that 4062 (not all labels are handled and default is missing) is\n// enabled\n#endif\n\n#if defined(__clang__)\n#pragma clang diagnostic push\n// For simplicity, benchmarking-only helpers are always enabled\n#pragma clang diagnostic ignored \"-Wunused-function\"\n#endif\n\nnamespace Catch {\n\nnamespace {\n\n// Formatter impl for ConsoleReporter\nclass ConsoleAssertionPrinter {\npublic:\n    ConsoleAssertionPrinter &operator=(ConsoleAssertionPrinter const &) = delete;\n    ConsoleAssertionPrinter(ConsoleAssertionPrinter const &) = delete;\n    ConsoleAssertionPrinter(std::ostream &_stream,\n                            AssertionStats const &_stats,\n                            bool _printInfoMessages)\n            : stream(_stream),\n              stats(_stats),\n              result(_stats.assertionResult),\n              colour(Colour::None),\n              message(result.getMessage()),\n              messages(_stats.infoMessages),\n              printInfoMessages(_printInfoMessages) {\n        switch (result.getResultType()) {\n        case ResultWas::Ok:\n            colour = Colour::Success;\n            passOrFail = \"PASSED\";\n            // if( result.hasMessage() )\n            if (_stats.infoMessages.size() == 1)\n                messageLabel = \"with message\";\n            if (_stats.infoMessages.size() > 1)\n                messageLabel = \"with messages\";\n            break;\n        case ResultWas::ExpressionFailed:\n            if (result.isOk()) {\n                colour = Colour::Success;\n                passOrFail = \"FAILED - but was ok\";\n            } else {\n                colour = Colour::Error;\n                passOrFail = \"FAILED\";\n            }\n            if (_stats.infoMessages.size() == 1)\n                messageLabel = \"with message\";\n            if (_stats.infoMessages.size() > 1)\n                messageLabel = \"with messages\";\n            break;\n        case ResultWas::ThrewException:\n            colour = Colour::Error;\n            passOrFail = \"FAILED\";\n            messageLabel = \"due to unexpected exception with \";\n            if (_stats.infoMessages.size() == 1)\n                messageLabel += \"message\";\n            if (_stats.infoMessages.size() > 1)\n                messageLabel += \"messages\";\n            break;\n        case ResultWas::FatalErrorCondition:\n            colour = Colour::Error;\n            passOrFail = \"FAILED\";\n            messageLabel = \"due to a fatal error condition\";\n            break;\n        case ResultWas::DidntThrowException:\n            colour = Colour::Error;\n            passOrFail = \"FAILED\";\n            messageLabel = \"because no exception was thrown where one was expected\";\n            break;\n        case ResultWas::Info:\n            messageLabel = \"info\";\n            break;\n        case ResultWas::Warning:\n            messageLabel = \"warning\";\n            break;\n        case ResultWas::ExplicitFailure:\n            passOrFail = \"FAILED\";\n            colour = Colour::Error;\n            if (_stats.infoMessages.size() == 1)\n                messageLabel = \"explicitly with message\";\n            if (_stats.infoMessages.size() > 1)\n                messageLabel = \"explicitly with messages\";\n            break;\n            // These cases are here to prevent compiler warnings\n        case ResultWas::Unknown:\n        case ResultWas::FailureBit:\n        case ResultWas::Exception:\n            passOrFail = \"** internal error **\";\n            colour = Colour::Error;\n            break;\n        }\n    }\n\n    void print() const {\n        printSourceInfo();\n        if (stats.totals.assertions.total() > 0) {\n            printResultType();\n            printOriginalExpression();\n            printReconstructedExpression();\n        } else {\n            stream << '\\n';\n        }\n        printMessage();\n    }\n\nprivate:\n    void printResultType() const {\n        if (!passOrFail.empty()) {\n            Colour colourGuard(colour);\n            stream << passOrFail << \":\\n\";\n        }\n    }\n    void printOriginalExpression() const {\n        if (result.hasExpression()) {\n            Colour colourGuard(Colour::OriginalExpression);\n            stream << \"  \";\n            stream << result.getExpressionInMacro();\n            stream << '\\n';\n        }\n    }\n    void printReconstructedExpression() const {\n        if (result.hasExpandedExpression()) {\n            stream << \"with expansion:\\n\";\n            Colour colourGuard(Colour::ReconstructedExpression);\n            stream << Column(result.getExpandedExpression()).indent(2) << '\\n';\n        }\n    }\n    void printMessage() const {\n        if (!messageLabel.empty())\n            stream << messageLabel << ':' << '\\n';\n        for (auto const &msg : messages) {\n            // If this assertion is a warning ignore any INFO messages\n            if (printInfoMessages || msg.type != ResultWas::Info)\n                stream << Column(msg.message).indent(2) << '\\n';\n        }\n    }\n    void printSourceInfo() const {\n        Colour colourGuard(Colour::FileName);\n        stream << result.getSourceInfo() << \": \";\n    }\n\n    std::ostream &stream;\n    AssertionStats const &stats;\n    AssertionResult const &result;\n    Colour::Code colour;\n    std::string passOrFail;\n    std::string messageLabel;\n    std::string message;\n    std::vector<MessageInfo> messages;\n    bool printInfoMessages;\n};\n\nstd::size_t makeRatio(std::size_t number, std::size_t total) {\n    std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0;\n    return (ratio == 0 && number > 0) ? 1 : ratio;\n}\n\nstd::size_t &findMax(std::size_t &i, std::size_t &j, std::size_t &k) {\n    if (i > j && i > k)\n        return i;\n    else if (j > k)\n        return j;\n    else\n        return k;\n}\n\nstruct ColumnInfo {\n    enum Justification { Left, Right };\n    std::string name;\n    int width;\n    Justification justification;\n};\nstruct ColumnBreak {};\nstruct RowBreak {};\n\nclass Duration {\n    enum class Unit { Auto, Nanoseconds, Microseconds, Milliseconds, Seconds, Minutes };\n    static const uint64_t s_nanosecondsInAMicrosecond = 1000;\n    static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond;\n    static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond;\n    static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond;\n\n    double m_inNanoseconds;\n    Unit m_units;\n\npublic:\n    explicit Duration(double inNanoseconds, Unit units = Unit::Auto)\n            : m_inNanoseconds(inNanoseconds), m_units(units) {\n        if (m_units == Unit::Auto) {\n            if (m_inNanoseconds < s_nanosecondsInAMicrosecond)\n                m_units = Unit::Nanoseconds;\n            else if (m_inNanoseconds < s_nanosecondsInAMillisecond)\n                m_units = Unit::Microseconds;\n            else if (m_inNanoseconds < s_nanosecondsInASecond)\n                m_units = Unit::Milliseconds;\n            else if (m_inNanoseconds < s_nanosecondsInAMinute)\n                m_units = Unit::Seconds;\n            else\n                m_units = Unit::Minutes;\n        }\n    }\n\n    auto value() const -> double {\n        switch (m_units) {\n        case Unit::Microseconds:\n            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMicrosecond);\n        case Unit::Milliseconds:\n            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMillisecond);\n        case Unit::Seconds:\n            return m_inNanoseconds / static_cast<double>(s_nanosecondsInASecond);\n        case Unit::Minutes:\n            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMinute);\n        default:\n            return m_inNanoseconds;\n        }\n    }\n    auto unitsAsString() const -> std::string {\n        switch (m_units) {\n        case Unit::Nanoseconds:\n            return \"ns\";\n        case Unit::Microseconds:\n            return \"us\";\n        case Unit::Milliseconds:\n            return \"ms\";\n        case Unit::Seconds:\n            return \"s\";\n        case Unit::Minutes:\n            return \"m\";\n        default:\n            return \"** internal error **\";\n        }\n    }\n    friend auto operator<<(std::ostream &os, Duration const &duration) -> std::ostream & {\n        return os << duration.value() << ' ' << duration.unitsAsString();\n    }\n};\n}  // namespace\n\nclass TablePrinter {\n    std::ostream &m_os;\n    std::vector<ColumnInfo> m_columnInfos;\n    std::ostringstream m_oss;\n    int m_currentColumn = -1;\n    bool m_isOpen = false;\n\npublic:\n    TablePrinter(std::ostream &os, std::vector<ColumnInfo> columnInfos)\n            : m_os(os), m_columnInfos(std::move(columnInfos)) {}\n\n    auto columnInfos() const -> std::vector<ColumnInfo> const & { return m_columnInfos; }\n\n    void open() {\n        if (!m_isOpen) {\n            m_isOpen = true;\n            *this << RowBreak();\n\n            Columns headerCols;\n            Spacer spacer(2);\n            for (auto const &info : m_columnInfos) {\n                headerCols += Column(info.name).width(static_cast<std::size_t>(info.width - 2));\n                headerCols += spacer;\n            }\n            m_os << headerCols << '\\n';\n\n            m_os << Catch::getLineOfChars<'-'>() << '\\n';\n        }\n    }\n    void close() {\n        if (m_isOpen) {\n            *this << RowBreak();\n            m_os << std::endl;\n            m_isOpen = false;\n        }\n    }\n\n    template <typename T>\n    friend TablePrinter &operator<<(TablePrinter &tp, T const &value) {\n        tp.m_oss << value;\n        return tp;\n    }\n\n    friend TablePrinter &operator<<(TablePrinter &tp, ColumnBreak) {\n        auto colStr = tp.m_oss.str();\n        const auto strSize = colStr.size();\n        tp.m_oss.str(\"\");\n        tp.open();\n        if (tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size() - 1)) {\n            tp.m_currentColumn = -1;\n            tp.m_os << '\\n';\n        }\n        tp.m_currentColumn++;\n\n        auto colInfo = tp.m_columnInfos[tp.m_currentColumn];\n        auto padding = (strSize + 1 < static_cast<std::size_t>(colInfo.width))\n                               ? std::string(colInfo.width - (strSize + 1), ' ')\n                               : std::string();\n        if (colInfo.justification == ColumnInfo::Left)\n            tp.m_os << colStr << padding << ' ';\n        else\n            tp.m_os << padding << colStr << ' ';\n        return tp;\n    }\n\n    friend TablePrinter &operator<<(TablePrinter &tp, RowBreak) {\n        if (tp.m_currentColumn > 0) {\n            tp.m_os << '\\n';\n            tp.m_currentColumn = -1;\n        }\n        return tp;\n    }\n};\n\nConsoleReporter::ConsoleReporter(ReporterConfig const &config)\n        : StreamingReporterBase(config),\n          m_tablePrinter(new TablePrinter(config.stream(), [&config]() -> std::vector<ColumnInfo> {\n              if (config.fullConfig()->benchmarkNoAnalysis()) {\n                  return {{\"benchmark name\", CATCH_CONFIG_CONSOLE_WIDTH - 43, ColumnInfo::Left},\n                          {\"     samples\", 14, ColumnInfo::Right},\n                          {\"  iterations\", 14, ColumnInfo::Right},\n                          {\"        mean\", 14, ColumnInfo::Right}};\n              } else {\n                  return {{\"benchmark name\", CATCH_CONFIG_CONSOLE_WIDTH - 43, ColumnInfo::Left},\n                          {\"samples      mean       std dev\", 14, ColumnInfo::Right},\n                          {\"iterations   low mean   low std dev\", 14, ColumnInfo::Right},\n                          {\"estimated    high mean  high std dev\", 14, ColumnInfo::Right}};\n              }\n          }())) {}\nConsoleReporter::~ConsoleReporter() = default;\n\nstd::string ConsoleReporter::getDescription() {\n    return \"Reports test results as plain lines of text\";\n}\n\nvoid ConsoleReporter::noMatchingTestCases(std::string const &spec) {\n    stream << \"No test cases matched '\" << spec << '\\'' << std::endl;\n}\n\nvoid ConsoleReporter::reportInvalidArguments(std::string const &arg) {\n    stream << \"Invalid Filter: \" << arg << std::endl;\n}\n\nvoid ConsoleReporter::assertionStarting(AssertionInfo const &) {}\n\nbool ConsoleReporter::assertionEnded(AssertionStats const &_assertionStats) {\n    AssertionResult const &result = _assertionStats.assertionResult;\n\n    bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();\n\n    // Drop out if result was successful but we're not printing them.\n    if (!includeResults && result.getResultType() != ResultWas::Warning)\n        return false;\n\n    lazyPrint();\n\n    ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults);\n    printer.print();\n    stream << std::endl;\n    return true;\n}\n\nvoid ConsoleReporter::sectionStarting(SectionInfo const &_sectionInfo) {\n    m_tablePrinter->close();\n    m_headerPrinted = false;\n    StreamingReporterBase::sectionStarting(_sectionInfo);\n}\nvoid ConsoleReporter::sectionEnded(SectionStats const &_sectionStats) {\n    m_tablePrinter->close();\n    if (_sectionStats.missingAssertions) {\n        lazyPrint();\n        Colour colour(Colour::ResultError);\n        if (m_sectionStack.size() > 1)\n            stream << \"\\nNo assertions in section\";\n        else\n            stream << \"\\nNo assertions in test case\";\n        stream << \" '\" << _sectionStats.sectionInfo.name << \"'\\n\" << std::endl;\n    }\n    double dur = _sectionStats.durationInSeconds;\n    if (shouldShowDuration(*m_config, dur)) {\n        stream << getFormattedDuration(dur) << \" s: \" << _sectionStats.sectionInfo.name\n               << std::endl;\n    }\n    if (m_headerPrinted) {\n        m_headerPrinted = false;\n    }\n    StreamingReporterBase::sectionEnded(_sectionStats);\n}\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\nvoid ConsoleReporter::benchmarkPreparing(std::string const &name) {\n    lazyPrintWithoutClosingBenchmarkTable();\n\n    auto nameCol = Column(name).width(\n            static_cast<std::size_t>(m_tablePrinter->columnInfos()[0].width - 2));\n\n    bool firstLine = true;\n    for (auto line : nameCol) {\n        if (!firstLine)\n            (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak();\n        else\n            firstLine = false;\n\n        (*m_tablePrinter) << line << ColumnBreak();\n    }\n}\n\nvoid ConsoleReporter::benchmarkStarting(BenchmarkInfo const &info) {\n    (*m_tablePrinter) << info.samples << ColumnBreak() << info.iterations << ColumnBreak();\n    if (!m_config->benchmarkNoAnalysis())\n        (*m_tablePrinter) << Duration(info.estimatedDuration) << ColumnBreak();\n}\nvoid ConsoleReporter::benchmarkEnded(BenchmarkStats<> const &stats) {\n    if (m_config->benchmarkNoAnalysis()) {\n        (*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak();\n    } else {\n        (*m_tablePrinter) << ColumnBreak() << Duration(stats.mean.point.count()) << ColumnBreak()\n                          << Duration(stats.mean.lower_bound.count()) << ColumnBreak()\n                          << Duration(stats.mean.upper_bound.count()) << ColumnBreak()\n                          << ColumnBreak() << Duration(stats.standardDeviation.point.count())\n                          << ColumnBreak() << Duration(stats.standardDeviation.lower_bound.count())\n                          << ColumnBreak() << Duration(stats.standardDeviation.upper_bound.count())\n                          << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak()\n                          << ColumnBreak();\n    }\n}\n\nvoid ConsoleReporter::benchmarkFailed(std::string const &error) {\n    Colour colour(Colour::Red);\n    (*m_tablePrinter) << \"Benchmark failed (\" << error << ')' << ColumnBreak() << RowBreak();\n}\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\nvoid ConsoleReporter::testCaseEnded(TestCaseStats const &_testCaseStats) {\n    m_tablePrinter->close();\n    StreamingReporterBase::testCaseEnded(_testCaseStats);\n    m_headerPrinted = false;\n}\nvoid ConsoleReporter::testGroupEnded(TestGroupStats const &_testGroupStats) {\n    if (currentGroupInfo.used) {\n        printSummaryDivider();\n        stream << \"Summary for group '\" << _testGroupStats.groupInfo.name << \"':\\n\";\n        printTotals(_testGroupStats.totals);\n        stream << '\\n' << std::endl;\n    }\n    StreamingReporterBase::testGroupEnded(_testGroupStats);\n}\nvoid ConsoleReporter::testRunEnded(TestRunStats const &_testRunStats) {\n    printTotalsDivider(_testRunStats.totals);\n    printTotals(_testRunStats.totals);\n    stream << std::endl;\n    StreamingReporterBase::testRunEnded(_testRunStats);\n}\nvoid ConsoleReporter::testRunStarting(TestRunInfo const &_testInfo) {\n    StreamingReporterBase::testRunStarting(_testInfo);\n    printTestFilters();\n}\n\nvoid ConsoleReporter::lazyPrint() {\n    m_tablePrinter->close();\n    lazyPrintWithoutClosingBenchmarkTable();\n}\n\nvoid ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() {\n    if (!currentTestRunInfo.used)\n        lazyPrintRunInfo();\n    if (!currentGroupInfo.used)\n        lazyPrintGroupInfo();\n\n    if (!m_headerPrinted) {\n        printTestCaseAndSectionHeader();\n        m_headerPrinted = true;\n    }\n}\nvoid ConsoleReporter::lazyPrintRunInfo() {\n    stream << '\\n' << getLineOfChars<'~'>() << '\\n';\n    Colour colour(Colour::SecondaryText);\n    stream << currentTestRunInfo->name << \" is a Catch v\" << libraryVersion()\n           << \" host application.\\n\"\n           << \"Run with -? for options\\n\\n\";\n\n    if (m_config->rngSeed() != 0)\n        stream << \"Randomness seeded to: \" << m_config->rngSeed() << \"\\n\\n\";\n\n    currentTestRunInfo.used = true;\n}\nvoid ConsoleReporter::lazyPrintGroupInfo() {\n    if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) {\n        printClosedHeader(\"Group: \" + currentGroupInfo->name);\n        currentGroupInfo.used = true;\n    }\n}\nvoid ConsoleReporter::printTestCaseAndSectionHeader() {\n    assert(!m_sectionStack.empty());\n    printOpenHeader(currentTestCaseInfo->name);\n\n    if (m_sectionStack.size() > 1) {\n        Colour colourGuard(Colour::Headers);\n\n        auto it = m_sectionStack.begin() + 1,  // Skip first section (test case)\n                itEnd = m_sectionStack.end();\n        for (; it != itEnd; ++it)\n            printHeaderString(it->name, 2);\n    }\n\n    SourceLineInfo lineInfo = m_sectionStack.back().lineInfo;\n\n    stream << getLineOfChars<'-'>() << '\\n';\n    Colour colourGuard(Colour::FileName);\n    stream << lineInfo << '\\n';\n    stream << getLineOfChars<'.'>() << '\\n' << std::endl;\n}\n\nvoid ConsoleReporter::printClosedHeader(std::string const &_name) {\n    printOpenHeader(_name);\n    stream << getLineOfChars<'.'>() << '\\n';\n}\nvoid ConsoleReporter::printOpenHeader(std::string const &_name) {\n    stream << getLineOfChars<'-'>() << '\\n';\n    {\n        Colour colourGuard(Colour::Headers);\n        printHeaderString(_name);\n    }\n}\n\n// if string has a : in first line will set indent to follow it on\n// subsequent lines\nvoid ConsoleReporter::printHeaderString(std::string const &_string, std::size_t indent) {\n    std::size_t i = _string.find(\": \");\n    if (i != std::string::npos)\n        i += 2;\n    else\n        i = 0;\n    stream << Column(_string).indent(indent + i).initialIndent(indent) << '\\n';\n}\n\nstruct SummaryColumn {\n    SummaryColumn(std::string _label, Colour::Code _colour)\n            : label(std::move(_label)), colour(_colour) {}\n    SummaryColumn addRow(std::size_t count) {\n        ReusableStringStream rss;\n        rss << count;\n        std::string row = rss.str();\n        for (auto &oldRow : rows) {\n            while (oldRow.size() < row.size())\n                oldRow = ' ' + oldRow;\n            while (oldRow.size() > row.size())\n                row = ' ' + row;\n        }\n        rows.push_back(row);\n        return *this;\n    }\n\n    std::string label;\n    Colour::Code colour;\n    std::vector<std::string> rows;\n};\n\nvoid ConsoleReporter::printTotals(Totals const &totals) {\n    if (totals.testCases.total() == 0) {\n        stream << Colour(Colour::Warning) << \"No tests ran\\n\";\n    } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) {\n        stream << Colour(Colour::ResultSuccess) << \"All tests passed\";\n        stream << \" (\" << pluralise(totals.assertions.passed, \"assertion\") << \" in \"\n               << pluralise(totals.testCases.passed, \"test case\") << ')' << '\\n';\n    } else {\n        std::vector<SummaryColumn> columns;\n        columns.push_back(SummaryColumn(\"\", Colour::None)\n                                  .addRow(totals.testCases.total())\n                                  .addRow(totals.assertions.total()));\n        columns.push_back(SummaryColumn(\"passed\", Colour::Success)\n                                  .addRow(totals.testCases.passed)\n                                  .addRow(totals.assertions.passed));\n        columns.push_back(SummaryColumn(\"failed\", Colour::ResultError)\n                                  .addRow(totals.testCases.failed)\n                                  .addRow(totals.assertions.failed));\n        columns.push_back(SummaryColumn(\"failed as expected\", Colour::ResultExpectedFailure)\n                                  .addRow(totals.testCases.failedButOk)\n                                  .addRow(totals.assertions.failedButOk));\n\n        printSummaryRow(\"test cases\", columns, 0);\n        printSummaryRow(\"assertions\", columns, 1);\n    }\n}\nvoid ConsoleReporter::printSummaryRow(std::string const &label,\n                                      std::vector<SummaryColumn> const &cols,\n                                      std::size_t row) {\n    for (auto col : cols) {\n        std::string value = col.rows[row];\n        if (col.label.empty()) {\n            stream << label << \": \";\n            if (value != \"0\")\n                stream << value;\n            else\n                stream << Colour(Colour::Warning) << \"- none -\";\n        } else if (value != \"0\") {\n            stream << Colour(Colour::LightGrey) << \" | \";\n            stream << Colour(col.colour) << value << ' ' << col.label;\n        }\n    }\n    stream << '\\n';\n}\n\nvoid ConsoleReporter::printTotalsDivider(Totals const &totals) {\n    if (totals.testCases.total() > 0) {\n        std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total());\n        std::size_t failedButOkRatio =\n                makeRatio(totals.testCases.failedButOk, totals.testCases.total());\n        std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total());\n        while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1)\n            findMax(failedRatio, failedButOkRatio, passedRatio)++;\n        while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1)\n            findMax(failedRatio, failedButOkRatio, passedRatio)--;\n\n        stream << Colour(Colour::Error) << std::string(failedRatio, '=');\n        stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '=');\n        if (totals.testCases.allPassed())\n            stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '=');\n        else\n            stream << Colour(Colour::Success) << std::string(passedRatio, '=');\n    } else {\n        stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '=');\n    }\n    stream << '\\n';\n}\nvoid ConsoleReporter::printSummaryDivider() { stream << getLineOfChars<'-'>() << '\\n'; }\n\nvoid ConsoleReporter::printTestFilters() {\n    if (m_config->testSpec().hasFilters()) {\n        Colour guard(Colour::BrightYellow);\n        stream << \"Filters: \" << serializeFilters(m_config->getTestsOrTags()) << '\\n';\n    }\n}\n\nCATCH_REGISTER_REPORTER(\"console\", ConsoleReporter)\n\n}  // end namespace Catch\n\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n// end catch_reporter_console.cpp\n// start catch_reporter_junit.cpp\n\n#include <algorithm>\n#include <cassert>\n#include <ctime>\n#include <iomanip>\n#include <sstream>\n\nnamespace Catch {\n\nnamespace {\nstd::string getCurrentTimestamp() {\n    // Beware, this is not reentrant because of backward compatibility issues\n    // Also, UTC only, again because of backward compatibility (%z is C++11)\n    time_t rawtime;\n    std::time(&rawtime);\n    auto const timeStampSize = sizeof(\"2017-01-16T17:06:45Z\");\n\n#ifdef _MSC_VER\n    std::tm timeInfo = {};\n    gmtime_s(&timeInfo, &rawtime);\n#else\n    std::tm *timeInfo;\n    timeInfo = std::gmtime(&rawtime);\n#endif\n\n    char timeStamp[timeStampSize];\n    const char *const fmt = \"%Y-%m-%dT%H:%M:%SZ\";\n\n#ifdef _MSC_VER\n    std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);\n#else\n    std::strftime(timeStamp, timeStampSize, fmt, timeInfo);\n#endif\n    return std::string(timeStamp, timeStampSize - 1);\n}\n\nstd::string fileNameTag(const std::vector<std::string> &tags) {\n    auto it = std::find_if(begin(tags), end(tags),\n                           [](std::string const &tag) { return tag.front() == '#'; });\n    if (it != tags.end())\n        return it->substr(1);\n    return std::string();\n}\n\n// Formats the duration in seconds to 3 decimal places.\n// This is done because some genius defined Maven Surefire schema\n// in a way that only accepts 3 decimal places, and tools like\n// Jenkins use that schema for validation JUnit reporter output.\nstd::string formatDuration(double seconds) {\n    ReusableStringStream rss;\n    rss << std::fixed << std::setprecision(3) << seconds;\n    return rss.str();\n}\n\n}  // anonymous namespace\n\nJunitReporter::JunitReporter(ReporterConfig const &_config)\n        : CumulativeReporterBase(_config), xml(_config.stream()) {\n    m_reporterPrefs.shouldRedirectStdOut = true;\n    m_reporterPrefs.shouldReportAllAssertions = true;\n}\n\nJunitReporter::~JunitReporter() {}\n\nstd::string JunitReporter::getDescription() {\n    return \"Reports test results in an XML format that looks like Ant's \"\n           \"junitreport target\";\n}\n\nvoid JunitReporter::noMatchingTestCases(std::string const & /*spec*/) {}\n\nvoid JunitReporter::testRunStarting(TestRunInfo const &runInfo) {\n    CumulativeReporterBase::testRunStarting(runInfo);\n    xml.startElement(\"testsuites\");\n}\n\nvoid JunitReporter::testGroupStarting(GroupInfo const &groupInfo) {\n    suiteTimer.start();\n    stdOutForSuite.clear();\n    stdErrForSuite.clear();\n    unexpectedExceptions = 0;\n    CumulativeReporterBase::testGroupStarting(groupInfo);\n}\n\nvoid JunitReporter::testCaseStarting(TestCaseInfo const &testCaseInfo) {\n    m_okToFail = testCaseInfo.okToFail();\n}\n\nbool JunitReporter::assertionEnded(AssertionStats const &assertionStats) {\n    if (assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail)\n        unexpectedExceptions++;\n    return CumulativeReporterBase::assertionEnded(assertionStats);\n}\n\nvoid JunitReporter::testCaseEnded(TestCaseStats const &testCaseStats) {\n    stdOutForSuite += testCaseStats.stdOut;\n    stdErrForSuite += testCaseStats.stdErr;\n    CumulativeReporterBase::testCaseEnded(testCaseStats);\n}\n\nvoid JunitReporter::testGroupEnded(TestGroupStats const &testGroupStats) {\n    double suiteTime = suiteTimer.getElapsedSeconds();\n    CumulativeReporterBase::testGroupEnded(testGroupStats);\n    writeGroup(*m_testGroups.back(), suiteTime);\n}\n\nvoid JunitReporter::testRunEndedCumulative() { xml.endElement(); }\n\nvoid JunitReporter::writeGroup(TestGroupNode const &groupNode, double suiteTime) {\n    XmlWriter::ScopedElement e = xml.scopedElement(\"testsuite\");\n\n    TestGroupStats const &stats = groupNode.value;\n    xml.writeAttribute(\"name\", stats.groupInfo.name);\n    xml.writeAttribute(\"errors\", unexpectedExceptions);\n    xml.writeAttribute(\"failures\", stats.totals.assertions.failed - unexpectedExceptions);\n    xml.writeAttribute(\"tests\", stats.totals.assertions.total());\n    xml.writeAttribute(\"hostname\", \"tbd\");  // !TBD\n    if (m_config->showDurations() == ShowDurations::Never)\n        xml.writeAttribute(\"time\", \"\");\n    else\n        xml.writeAttribute(\"time\", formatDuration(suiteTime));\n    xml.writeAttribute(\"timestamp\", getCurrentTimestamp());\n\n    // Write properties if there are any\n    if (m_config->hasTestFilters() || m_config->rngSeed() != 0) {\n        auto properties = xml.scopedElement(\"properties\");\n        if (m_config->hasTestFilters()) {\n            xml.scopedElement(\"property\")\n                    .writeAttribute(\"name\", \"filters\")\n                    .writeAttribute(\"value\", serializeFilters(m_config->getTestsOrTags()));\n        }\n        if (m_config->rngSeed() != 0) {\n            xml.scopedElement(\"property\")\n                    .writeAttribute(\"name\", \"random-seed\")\n                    .writeAttribute(\"value\", m_config->rngSeed());\n        }\n    }\n\n    // Write test cases\n    for (auto const &child : groupNode.children)\n        writeTestCase(*child);\n\n    xml.scopedElement(\"system-out\").writeText(trim(stdOutForSuite), XmlFormatting::Newline);\n    xml.scopedElement(\"system-err\").writeText(trim(stdErrForSuite), XmlFormatting::Newline);\n}\n\nvoid JunitReporter::writeTestCase(TestCaseNode const &testCaseNode) {\n    TestCaseStats const &stats = testCaseNode.value;\n\n    // All test cases have exactly one section - which represents the\n    // test case itself. That section may have 0-n nested sections\n    assert(testCaseNode.children.size() == 1);\n    SectionNode const &rootSection = *testCaseNode.children.front();\n\n    std::string className = stats.testInfo.className;\n\n    if (className.empty()) {\n        className = fileNameTag(stats.testInfo.tags);\n        if (className.empty())\n            className = \"global\";\n    }\n\n    if (!m_config->name().empty())\n        className = m_config->name() + \".\" + className;\n\n    writeSection(className, \"\", rootSection, stats.testInfo.okToFail());\n}\n\nvoid JunitReporter::writeSection(std::string const &className,\n                                 std::string const &rootName,\n                                 SectionNode const &sectionNode,\n                                 bool testOkToFail) {\n    std::string name = trim(sectionNode.stats.sectionInfo.name);\n    if (!rootName.empty())\n        name = rootName + '/' + name;\n\n    if (!sectionNode.assertions.empty() || !sectionNode.stdOut.empty() ||\n        !sectionNode.stdErr.empty()) {\n        XmlWriter::ScopedElement e = xml.scopedElement(\"testcase\");\n        if (className.empty()) {\n            xml.writeAttribute(\"classname\", name);\n            xml.writeAttribute(\"name\", \"root\");\n        } else {\n            xml.writeAttribute(\"classname\", className);\n            xml.writeAttribute(\"name\", name);\n        }\n        xml.writeAttribute(\"time\", formatDuration(sectionNode.stats.durationInSeconds));\n        // This is not ideal, but it should be enough to mimic gtest's\n        // junit output.\n        // Ideally the JUnit reporter would also handle `skipTest`\n        // events and write those out appropriately.\n        xml.writeAttribute(\"status\", \"run\");\n\n        if (sectionNode.stats.assertions.failedButOk) {\n            xml.scopedElement(\"skipped\").writeAttribute(\"message\",\n                                                        \"TEST_CASE tagged with !mayfail\");\n        }\n\n        writeAssertions(sectionNode);\n\n        if (!sectionNode.stdOut.empty())\n            xml.scopedElement(\"system-out\")\n                    .writeText(trim(sectionNode.stdOut), XmlFormatting::Newline);\n        if (!sectionNode.stdErr.empty())\n            xml.scopedElement(\"system-err\")\n                    .writeText(trim(sectionNode.stdErr), XmlFormatting::Newline);\n    }\n    for (auto const &childNode : sectionNode.childSections)\n        if (className.empty())\n            writeSection(name, \"\", *childNode, testOkToFail);\n        else\n            writeSection(className, name, *childNode, testOkToFail);\n}\n\nvoid JunitReporter::writeAssertions(SectionNode const &sectionNode) {\n    for (auto const &assertion : sectionNode.assertions)\n        writeAssertion(assertion);\n}\n\nvoid JunitReporter::writeAssertion(AssertionStats const &stats) {\n    AssertionResult const &result = stats.assertionResult;\n    if (!result.isOk()) {\n        std::string elementName;\n        switch (result.getResultType()) {\n        case ResultWas::ThrewException:\n        case ResultWas::FatalErrorCondition:\n            elementName = \"error\";\n            break;\n        case ResultWas::ExplicitFailure:\n        case ResultWas::ExpressionFailed:\n        case ResultWas::DidntThrowException:\n            elementName = \"failure\";\n            break;\n\n        // We should never see these here:\n        case ResultWas::Info:\n        case ResultWas::Warning:\n        case ResultWas::Ok:\n        case ResultWas::Unknown:\n        case ResultWas::FailureBit:\n        case ResultWas::Exception:\n            elementName = \"internalError\";\n            break;\n        }\n\n        XmlWriter::ScopedElement e = xml.scopedElement(elementName);\n\n        xml.writeAttribute(\"message\", result.getExpression());\n        xml.writeAttribute(\"type\", result.getTestMacroName());\n\n        ReusableStringStream rss;\n        if (stats.totals.assertions.total() > 0) {\n            rss << \"FAILED\"\n                << \":\\n\";\n            if (result.hasExpression()) {\n                rss << \"  \";\n                rss << result.getExpressionInMacro();\n                rss << '\\n';\n            }\n            if (result.hasExpandedExpression()) {\n                rss << \"with expansion:\\n\";\n                rss << Column(result.getExpandedExpression()).indent(2) << '\\n';\n            }\n        } else {\n            rss << '\\n';\n        }\n\n        if (!result.getMessage().empty())\n            rss << result.getMessage() << '\\n';\n        for (auto const &msg : stats.infoMessages)\n            if (msg.type == ResultWas::Info)\n                rss << msg.message << '\\n';\n\n        rss << \"at \" << result.getSourceInfo();\n        xml.writeText(rss.str(), XmlFormatting::Newline);\n    }\n}\n\nCATCH_REGISTER_REPORTER(\"junit\", JunitReporter)\n\n}  // end namespace Catch\n// end catch_reporter_junit.cpp\n// start catch_reporter_listening.cpp\n\n#include <cassert>\n\nnamespace Catch {\n\nListeningReporter::ListeningReporter() {\n    // We will assume that listeners will always want all assertions\n    m_preferences.shouldReportAllAssertions = true;\n}\n\nvoid ListeningReporter::addListener(IStreamingReporterPtr &&listener) {\n    m_listeners.push_back(std::move(listener));\n}\n\nvoid ListeningReporter::addReporter(IStreamingReporterPtr &&reporter) {\n    assert(!m_reporter && \"Listening reporter can wrap only 1 real reporter\");\n    m_reporter = std::move(reporter);\n    m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut;\n}\n\nReporterPreferences ListeningReporter::getPreferences() const { return m_preferences; }\n\nstd::set<Verbosity> ListeningReporter::getSupportedVerbosities() { return std::set<Verbosity>{}; }\n\nvoid ListeningReporter::noMatchingTestCases(std::string const &spec) {\n    for (auto const &listener : m_listeners) {\n        listener->noMatchingTestCases(spec);\n    }\n    m_reporter->noMatchingTestCases(spec);\n}\n\nvoid ListeningReporter::reportInvalidArguments(std::string const &arg) {\n    for (auto const &listener : m_listeners) {\n        listener->reportInvalidArguments(arg);\n    }\n    m_reporter->reportInvalidArguments(arg);\n}\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\nvoid ListeningReporter::benchmarkPreparing(std::string const &name) {\n    for (auto const &listener : m_listeners) {\n        listener->benchmarkPreparing(name);\n    }\n    m_reporter->benchmarkPreparing(name);\n}\nvoid ListeningReporter::benchmarkStarting(BenchmarkInfo const &benchmarkInfo) {\n    for (auto const &listener : m_listeners) {\n        listener->benchmarkStarting(benchmarkInfo);\n    }\n    m_reporter->benchmarkStarting(benchmarkInfo);\n}\nvoid ListeningReporter::benchmarkEnded(BenchmarkStats<> const &benchmarkStats) {\n    for (auto const &listener : m_listeners) {\n        listener->benchmarkEnded(benchmarkStats);\n    }\n    m_reporter->benchmarkEnded(benchmarkStats);\n}\n\nvoid ListeningReporter::benchmarkFailed(std::string const &error) {\n    for (auto const &listener : m_listeners) {\n        listener->benchmarkFailed(error);\n    }\n    m_reporter->benchmarkFailed(error);\n}\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\nvoid ListeningReporter::testRunStarting(TestRunInfo const &testRunInfo) {\n    for (auto const &listener : m_listeners) {\n        listener->testRunStarting(testRunInfo);\n    }\n    m_reporter->testRunStarting(testRunInfo);\n}\n\nvoid ListeningReporter::testGroupStarting(GroupInfo const &groupInfo) {\n    for (auto const &listener : m_listeners) {\n        listener->testGroupStarting(groupInfo);\n    }\n    m_reporter->testGroupStarting(groupInfo);\n}\n\nvoid ListeningReporter::testCaseStarting(TestCaseInfo const &testInfo) {\n    for (auto const &listener : m_listeners) {\n        listener->testCaseStarting(testInfo);\n    }\n    m_reporter->testCaseStarting(testInfo);\n}\n\nvoid ListeningReporter::sectionStarting(SectionInfo const &sectionInfo) {\n    for (auto const &listener : m_listeners) {\n        listener->sectionStarting(sectionInfo);\n    }\n    m_reporter->sectionStarting(sectionInfo);\n}\n\nvoid ListeningReporter::assertionStarting(AssertionInfo const &assertionInfo) {\n    for (auto const &listener : m_listeners) {\n        listener->assertionStarting(assertionInfo);\n    }\n    m_reporter->assertionStarting(assertionInfo);\n}\n\n// The return value indicates if the messages buffer should be cleared:\nbool ListeningReporter::assertionEnded(AssertionStats const &assertionStats) {\n    for (auto const &listener : m_listeners) {\n        static_cast<void>(listener->assertionEnded(assertionStats));\n    }\n    return m_reporter->assertionEnded(assertionStats);\n}\n\nvoid ListeningReporter::sectionEnded(SectionStats const &sectionStats) {\n    for (auto const &listener : m_listeners) {\n        listener->sectionEnded(sectionStats);\n    }\n    m_reporter->sectionEnded(sectionStats);\n}\n\nvoid ListeningReporter::testCaseEnded(TestCaseStats const &testCaseStats) {\n    for (auto const &listener : m_listeners) {\n        listener->testCaseEnded(testCaseStats);\n    }\n    m_reporter->testCaseEnded(testCaseStats);\n}\n\nvoid ListeningReporter::testGroupEnded(TestGroupStats const &testGroupStats) {\n    for (auto const &listener : m_listeners) {\n        listener->testGroupEnded(testGroupStats);\n    }\n    m_reporter->testGroupEnded(testGroupStats);\n}\n\nvoid ListeningReporter::testRunEnded(TestRunStats const &testRunStats) {\n    for (auto const &listener : m_listeners) {\n        listener->testRunEnded(testRunStats);\n    }\n    m_reporter->testRunEnded(testRunStats);\n}\n\nvoid ListeningReporter::skipTest(TestCaseInfo const &testInfo) {\n    for (auto const &listener : m_listeners) {\n        listener->skipTest(testInfo);\n    }\n    m_reporter->skipTest(testInfo);\n}\n\nbool ListeningReporter::isMulti() const { return true; }\n\n}  // end namespace Catch\n// end catch_reporter_listening.cpp\n// start catch_reporter_xml.cpp\n\n#if defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable : 4061)  // Not all labels are EXPLICITLY handled in  \\\n                                 // switch Note that 4062 (not all labels are \\\n                                 // handled and default is missing) is enabled\n#endif\n\nnamespace Catch {\nXmlReporter::XmlReporter(ReporterConfig const &_config)\n        : StreamingReporterBase(_config), m_xml(_config.stream()) {\n    m_reporterPrefs.shouldRedirectStdOut = true;\n    m_reporterPrefs.shouldReportAllAssertions = true;\n}\n\nXmlReporter::~XmlReporter() = default;\n\nstd::string XmlReporter::getDescription() { return \"Reports test results as an XML document\"; }\n\nstd::string XmlReporter::getStylesheetRef() const { return std::string(); }\n\nvoid XmlReporter::writeSourceInfo(SourceLineInfo const &sourceInfo) {\n    m_xml.writeAttribute(\"filename\", sourceInfo.file).writeAttribute(\"line\", sourceInfo.line);\n}\n\nvoid XmlReporter::noMatchingTestCases(std::string const &s) {\n    StreamingReporterBase::noMatchingTestCases(s);\n}\n\nvoid XmlReporter::testRunStarting(TestRunInfo const &testInfo) {\n    StreamingReporterBase::testRunStarting(testInfo);\n    std::string stylesheetRef = getStylesheetRef();\n    if (!stylesheetRef.empty())\n        m_xml.writeStylesheetRef(stylesheetRef);\n    m_xml.startElement(\"Catch\");\n    if (!m_config->name().empty())\n        m_xml.writeAttribute(\"name\", m_config->name());\n    if (m_config->testSpec().hasFilters())\n        m_xml.writeAttribute(\"filters\", serializeFilters(m_config->getTestsOrTags()));\n    if (m_config->rngSeed() != 0)\n        m_xml.scopedElement(\"Randomness\").writeAttribute(\"seed\", m_config->rngSeed());\n}\n\nvoid XmlReporter::testGroupStarting(GroupInfo const &groupInfo) {\n    StreamingReporterBase::testGroupStarting(groupInfo);\n    m_xml.startElement(\"Group\").writeAttribute(\"name\", groupInfo.name);\n}\n\nvoid XmlReporter::testCaseStarting(TestCaseInfo const &testInfo) {\n    StreamingReporterBase::testCaseStarting(testInfo);\n    m_xml.startElement(\"TestCase\")\n            .writeAttribute(\"name\", trim(testInfo.name))\n            .writeAttribute(\"description\", testInfo.description)\n            .writeAttribute(\"tags\", testInfo.tagsAsString());\n\n    writeSourceInfo(testInfo.lineInfo);\n\n    if (m_config->showDurations() == ShowDurations::Always)\n        m_testCaseTimer.start();\n    m_xml.ensureTagClosed();\n}\n\nvoid XmlReporter::sectionStarting(SectionInfo const &sectionInfo) {\n    StreamingReporterBase::sectionStarting(sectionInfo);\n    if (m_sectionDepth++ > 0) {\n        m_xml.startElement(\"Section\").writeAttribute(\"name\", trim(sectionInfo.name));\n        writeSourceInfo(sectionInfo.lineInfo);\n        m_xml.ensureTagClosed();\n    }\n}\n\nvoid XmlReporter::assertionStarting(AssertionInfo const &) {}\n\nbool XmlReporter::assertionEnded(AssertionStats const &assertionStats) {\n    AssertionResult const &result = assertionStats.assertionResult;\n\n    bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();\n\n    if (includeResults || result.getResultType() == ResultWas::Warning) {\n        // Print any info messages in <Info> tags.\n        for (auto const &msg : assertionStats.infoMessages) {\n            if (msg.type == ResultWas::Info && includeResults) {\n                m_xml.scopedElement(\"Info\").writeText(msg.message);\n            } else if (msg.type == ResultWas::Warning) {\n                m_xml.scopedElement(\"Warning\").writeText(msg.message);\n            }\n        }\n    }\n\n    // Drop out if result was successful but we're not printing them.\n    if (!includeResults && result.getResultType() != ResultWas::Warning)\n        return true;\n\n    // Print the expression if there is one.\n    if (result.hasExpression()) {\n        m_xml.startElement(\"Expression\")\n                .writeAttribute(\"success\", result.succeeded())\n                .writeAttribute(\"type\", result.getTestMacroName());\n\n        writeSourceInfo(result.getSourceInfo());\n\n        m_xml.scopedElement(\"Original\").writeText(result.getExpression());\n        m_xml.scopedElement(\"Expanded\").writeText(result.getExpandedExpression());\n    }\n\n    // And... Print a result applicable to each result type.\n    switch (result.getResultType()) {\n    case ResultWas::ThrewException:\n        m_xml.startElement(\"Exception\");\n        writeSourceInfo(result.getSourceInfo());\n        m_xml.writeText(result.getMessage());\n        m_xml.endElement();\n        break;\n    case ResultWas::FatalErrorCondition:\n        m_xml.startElement(\"FatalErrorCondition\");\n        writeSourceInfo(result.getSourceInfo());\n        m_xml.writeText(result.getMessage());\n        m_xml.endElement();\n        break;\n    case ResultWas::Info:\n        m_xml.scopedElement(\"Info\").writeText(result.getMessage());\n        break;\n    case ResultWas::Warning:\n        // Warning will already have been written\n        break;\n    case ResultWas::ExplicitFailure:\n        m_xml.startElement(\"Failure\");\n        writeSourceInfo(result.getSourceInfo());\n        m_xml.writeText(result.getMessage());\n        m_xml.endElement();\n        break;\n    default:\n        break;\n    }\n\n    if (result.hasExpression())\n        m_xml.endElement();\n\n    return true;\n}\n\nvoid XmlReporter::sectionEnded(SectionStats const &sectionStats) {\n    StreamingReporterBase::sectionEnded(sectionStats);\n    if (--m_sectionDepth > 0) {\n        XmlWriter::ScopedElement e = m_xml.scopedElement(\"OverallResults\");\n        e.writeAttribute(\"successes\", sectionStats.assertions.passed);\n        e.writeAttribute(\"failures\", sectionStats.assertions.failed);\n        e.writeAttribute(\"expectedFailures\", sectionStats.assertions.failedButOk);\n\n        if (m_config->showDurations() == ShowDurations::Always)\n            e.writeAttribute(\"durationInSeconds\", sectionStats.durationInSeconds);\n\n        m_xml.endElement();\n    }\n}\n\nvoid XmlReporter::testCaseEnded(TestCaseStats const &testCaseStats) {\n    StreamingReporterBase::testCaseEnded(testCaseStats);\n    XmlWriter::ScopedElement e = m_xml.scopedElement(\"OverallResult\");\n    e.writeAttribute(\"success\", testCaseStats.totals.assertions.allOk());\n\n    if (m_config->showDurations() == ShowDurations::Always)\n        e.writeAttribute(\"durationInSeconds\", m_testCaseTimer.getElapsedSeconds());\n\n    if (!testCaseStats.stdOut.empty())\n        m_xml.scopedElement(\"StdOut\").writeText(trim(testCaseStats.stdOut), XmlFormatting::Newline);\n    if (!testCaseStats.stdErr.empty())\n        m_xml.scopedElement(\"StdErr\").writeText(trim(testCaseStats.stdErr), XmlFormatting::Newline);\n\n    m_xml.endElement();\n}\n\nvoid XmlReporter::testGroupEnded(TestGroupStats const &testGroupStats) {\n    StreamingReporterBase::testGroupEnded(testGroupStats);\n    // TODO: Check testGroupStats.aborting and act accordingly.\n    m_xml.scopedElement(\"OverallResults\")\n            .writeAttribute(\"successes\", testGroupStats.totals.assertions.passed)\n            .writeAttribute(\"failures\", testGroupStats.totals.assertions.failed)\n            .writeAttribute(\"expectedFailures\", testGroupStats.totals.assertions.failedButOk);\n    m_xml.scopedElement(\"OverallResultsCases\")\n            .writeAttribute(\"successes\", testGroupStats.totals.testCases.passed)\n            .writeAttribute(\"failures\", testGroupStats.totals.testCases.failed)\n            .writeAttribute(\"expectedFailures\", testGroupStats.totals.testCases.failedButOk);\n    m_xml.endElement();\n}\n\nvoid XmlReporter::testRunEnded(TestRunStats const &testRunStats) {\n    StreamingReporterBase::testRunEnded(testRunStats);\n    m_xml.scopedElement(\"OverallResults\")\n            .writeAttribute(\"successes\", testRunStats.totals.assertions.passed)\n            .writeAttribute(\"failures\", testRunStats.totals.assertions.failed)\n            .writeAttribute(\"expectedFailures\", testRunStats.totals.assertions.failedButOk);\n    m_xml.scopedElement(\"OverallResultsCases\")\n            .writeAttribute(\"successes\", testRunStats.totals.testCases.passed)\n            .writeAttribute(\"failures\", testRunStats.totals.testCases.failed)\n            .writeAttribute(\"expectedFailures\", testRunStats.totals.testCases.failedButOk);\n    m_xml.endElement();\n}\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\nvoid XmlReporter::benchmarkPreparing(std::string const &name) {\n    m_xml.startElement(\"BenchmarkResults\").writeAttribute(\"name\", name);\n}\n\nvoid XmlReporter::benchmarkStarting(BenchmarkInfo const &info) {\n    m_xml.writeAttribute(\"samples\", info.samples)\n            .writeAttribute(\"resamples\", info.resamples)\n            .writeAttribute(\"iterations\", info.iterations)\n            .writeAttribute(\"clockResolution\", info.clockResolution)\n            .writeAttribute(\"estimatedDuration\", info.estimatedDuration)\n            .writeComment(\"All values in nano seconds\");\n}\n\nvoid XmlReporter::benchmarkEnded(BenchmarkStats<> const &benchmarkStats) {\n    m_xml.startElement(\"mean\")\n            .writeAttribute(\"value\", benchmarkStats.mean.point.count())\n            .writeAttribute(\"lowerBound\", benchmarkStats.mean.lower_bound.count())\n            .writeAttribute(\"upperBound\", benchmarkStats.mean.upper_bound.count())\n            .writeAttribute(\"ci\", benchmarkStats.mean.confidence_interval);\n    m_xml.endElement();\n    m_xml.startElement(\"standardDeviation\")\n            .writeAttribute(\"value\", benchmarkStats.standardDeviation.point.count())\n            .writeAttribute(\"lowerBound\", benchmarkStats.standardDeviation.lower_bound.count())\n            .writeAttribute(\"upperBound\", benchmarkStats.standardDeviation.upper_bound.count())\n            .writeAttribute(\"ci\", benchmarkStats.standardDeviation.confidence_interval);\n    m_xml.endElement();\n    m_xml.startElement(\"outliers\")\n            .writeAttribute(\"variance\", benchmarkStats.outlierVariance)\n            .writeAttribute(\"lowMild\", benchmarkStats.outliers.low_mild)\n            .writeAttribute(\"lowSevere\", benchmarkStats.outliers.low_severe)\n            .writeAttribute(\"highMild\", benchmarkStats.outliers.high_mild)\n            .writeAttribute(\"highSevere\", benchmarkStats.outliers.high_severe);\n    m_xml.endElement();\n    m_xml.endElement();\n}\n\nvoid XmlReporter::benchmarkFailed(std::string const &error) {\n    m_xml.scopedElement(\"failed\").writeAttribute(\"message\", error);\n    m_xml.endElement();\n}\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\nCATCH_REGISTER_REPORTER(\"xml\", XmlReporter)\n\n}  // end namespace Catch\n\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n// end catch_reporter_xml.cpp\n\nnamespace Catch {\nLeakDetector leakDetector;\n}\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n// end catch_impl.hpp\n#endif\n\n#ifdef CATCH_CONFIG_MAIN\n// start catch_default_main.hpp\n\n#ifndef __OBJC__\n\n#if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && \\\n        !defined(DO_NOT_USE_WMAIN)\n// Standard C/C++ Win32 Unicode wmain entry point\nextern \"C\" int wmain(int argc, wchar_t *argv[], wchar_t *[]) {\n#else\n// Standard C/C++ main entry point\nint main(int argc, char *argv[]) {\n#endif\n\n    return Catch::Session().run(argc, argv);\n}\n\n#else  // __OBJC__\n\n// Objective-C entry point\nint main(int argc, char *const argv[]) {\n#if !CATCH_ARC_ENABLED\n    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];\n#endif\n\n    Catch::registerTestMethods();\n    int result = Catch::Session().run(argc, (char **)argv);\n\n#if !CATCH_ARC_ENABLED\n    [pool drain];\n#endif\n\n    return result;\n}\n\n#endif  // __OBJC__\n\n// end catch_default_main.hpp\n#endif\n\n#if !defined(CATCH_CONFIG_IMPL_ONLY)\n\n#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED\n#undef CLARA_CONFIG_MAIN\n#endif\n\n#if !defined(CATCH_CONFIG_DISABLE)\n//////\n// If this config identifier is defined then all CATCH macros are prefixed with\n// CATCH_\n#ifdef CATCH_CONFIG_PREFIX_ALL\n\n#define CATCH_REQUIRE(...) \\\n    INTERNAL_CATCH_TEST(\"CATCH_REQUIRE\", Catch::ResultDisposition::Normal, __VA_ARGS__)\n#define CATCH_REQUIRE_FALSE(...)                                                                \\\n    INTERNAL_CATCH_TEST(\"CATCH_REQUIRE_FALSE\",                                                  \\\n                        Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, \\\n                        __VA_ARGS__)\n\n#define CATCH_REQUIRE_THROWS(...) \\\n    INTERNAL_CATCH_THROWS(\"CATCH_REQUIRE_THROWS\", Catch::ResultDisposition::Normal, __VA_ARGS__)\n#define CATCH_REQUIRE_THROWS_AS(expr, exceptionType)                   \\\n    INTERNAL_CATCH_THROWS_AS(\"CATCH_REQUIRE_THROWS_AS\", exceptionType, \\\n                             Catch::ResultDisposition::Normal, expr)\n#define CATCH_REQUIRE_THROWS_WITH(expr, matcher)                   \\\n    INTERNAL_CATCH_THROWS_STR_MATCHES(\"CATCH_REQUIRE_THROWS_WITH\", \\\n                                      Catch::ResultDisposition::Normal, matcher, expr)\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher)               \\\n    INTERNAL_CATCH_THROWS_MATCHES(\"CATCH_REQUIRE_THROWS_MATCHES\", exceptionType, \\\n                                  Catch::ResultDisposition::Normal, matcher, expr)\n#endif  // CATCH_CONFIG_DISABLE_MATCHERS\n#define CATCH_REQUIRE_NOTHROW(...) \\\n    INTERNAL_CATCH_NO_THROW(\"CATCH_REQUIRE_NOTHROW\", Catch::ResultDisposition::Normal, __VA_ARGS__)\n\n#define CATCH_CHECK(...) \\\n    INTERNAL_CATCH_TEST(\"CATCH_CHECK\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__)\n#define CATCH_CHECK_FALSE(...)                                                                 \\\n    INTERNAL_CATCH_TEST(                                                                       \\\n            \"CATCH_CHECK_FALSE\",                                                               \\\n            Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, \\\n            __VA_ARGS__)\n#define CATCH_CHECKED_IF(...) \\\n    INTERNAL_CATCH_IF(\"CATCH_CHECKED_IF\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__)\n#define CATCH_CHECKED_ELSE(...)                                                            \\\n    INTERNAL_CATCH_ELSE(\"CATCH_CHECKED_ELSE\", Catch::ResultDisposition::ContinueOnFailure, \\\n                        __VA_ARGS__)\n#define CATCH_CHECK_NOFAIL(...)                                                                   \\\n    INTERNAL_CATCH_TEST(                                                                          \\\n            \"CATCH_CHECK_NOFAIL\",                                                                 \\\n            Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, \\\n            __VA_ARGS__)\n\n#define CATCH_CHECK_THROWS(...)                                                              \\\n    INTERNAL_CATCH_THROWS(\"CATCH_CHECK_THROWS\", Catch::ResultDisposition::ContinueOnFailure, \\\n                          __VA_ARGS__)\n#define CATCH_CHECK_THROWS_AS(expr, exceptionType)                   \\\n    INTERNAL_CATCH_THROWS_AS(\"CATCH_CHECK_THROWS_AS\", exceptionType, \\\n                             Catch::ResultDisposition::ContinueOnFailure, expr)\n#define CATCH_CHECK_THROWS_WITH(expr, matcher)                   \\\n    INTERNAL_CATCH_THROWS_STR_MATCHES(\"CATCH_CHECK_THROWS_WITH\", \\\n                                      Catch::ResultDisposition::ContinueOnFailure, matcher, expr)\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_CHECK_THROWS_MATCHES(expr, exceptionType, matcher)               \\\n    INTERNAL_CATCH_THROWS_MATCHES(\"CATCH_CHECK_THROWS_MATCHES\", exceptionType, \\\n                                  Catch::ResultDisposition::ContinueOnFailure, matcher, expr)\n#endif  // CATCH_CONFIG_DISABLE_MATCHERS\n#define CATCH_CHECK_NOTHROW(...)                                                                \\\n    INTERNAL_CATCH_NO_THROW(\"CATCH_CHECK_NOTHROW\", Catch::ResultDisposition::ContinueOnFailure, \\\n                            __VA_ARGS__)\n\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_CHECK_THAT(arg, matcher)                                                            \\\n    INTERNAL_CHECK_THAT(\"CATCH_CHECK_THAT\", matcher, Catch::ResultDisposition::ContinueOnFailure, \\\n                        arg)\n\n#define CATCH_REQUIRE_THAT(arg, matcher) \\\n    INTERNAL_CHECK_THAT(\"CATCH_REQUIRE_THAT\", matcher, Catch::ResultDisposition::Normal, arg)\n#endif  // CATCH_CONFIG_DISABLE_MATCHERS\n\n#define CATCH_INFO(msg) INTERNAL_CATCH_INFO(\"CATCH_INFO\", msg)\n#define CATCH_UNSCOPED_INFO(msg) INTERNAL_CATCH_UNSCOPED_INFO(\"CATCH_UNSCOPED_INFO\", msg)\n#define CATCH_WARN(msg)                                         \\\n    INTERNAL_CATCH_MSG(\"CATCH_WARN\", Catch::ResultWas::Warning, \\\n                       Catch::ResultDisposition::ContinueOnFailure, msg)\n#define CATCH_CAPTURE(...) \\\n    INTERNAL_CATCH_CAPTURE(INTERNAL_CATCH_UNIQUE_NAME(capturer), \"CATCH_CAPTURE\", __VA_ARGS__)\n\n#define CATCH_TEST_CASE(...) INTERNAL_CATCH_TESTCASE(__VA_ARGS__)\n#define CATCH_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_TEST_CASE_METHOD(className, __VA_ARGS__)\n#define CATCH_METHOD_AS_TEST_CASE(method, ...) \\\n    INTERNAL_CATCH_METHOD_AS_TEST_CASE(method, __VA_ARGS__)\n#define CATCH_REGISTER_TEST_CASE(Function, ...) \\\n    INTERNAL_CATCH_REGISTER_TESTCASE(Function, __VA_ARGS__)\n#define CATCH_SECTION(...) INTERNAL_CATCH_SECTION(__VA_ARGS__)\n#define CATCH_DYNAMIC_SECTION(...) INTERNAL_CATCH_DYNAMIC_SECTION(__VA_ARGS__)\n#define CATCH_FAIL(...)                                                 \\\n    INTERNAL_CATCH_MSG(\"CATCH_FAIL\", Catch::ResultWas::ExplicitFailure, \\\n                       Catch::ResultDisposition::Normal, __VA_ARGS__)\n#define CATCH_FAIL_CHECK(...)                                                 \\\n    INTERNAL_CATCH_MSG(\"CATCH_FAIL_CHECK\", Catch::ResultWas::ExplicitFailure, \\\n                       Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__)\n#define CATCH_SUCCEED(...)                                    \\\n    INTERNAL_CATCH_MSG(\"CATCH_SUCCEED\", Catch::ResultWas::Ok, \\\n                       Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__)\n\n#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE()\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define CATCH_TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__)\n#define CATCH_TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(__VA_ARGS__)\n#define CATCH_TEMPLATE_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__)\n#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, __VA_ARGS__)\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(__VA_ARGS__)\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(...) \\\n    INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(__VA_ARGS__)\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, __VA_ARGS__)\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) \\\n    INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, __VA_ARGS__)\n#else\n#define CATCH_TEMPLATE_TEST_CASE(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__))\n#define CATCH_TEMPLATE_TEST_CASE_SIG(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(__VA_ARGS__))\n#define CATCH_TEMPLATE_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__))\n#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(                            \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, __VA_ARGS__))\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(__VA_ARGS__))\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(__VA_ARGS__))\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(                                \\\n            INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, __VA_ARGS__))\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(                                    \\\n            INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, __VA_ARGS__))\n#endif\n\n#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)\n#define CATCH_STATIC_REQUIRE(...)             \\\n    static_assert(__VA_ARGS__, #__VA_ARGS__); \\\n    CATCH_SUCCEED(#__VA_ARGS__)\n#define CATCH_STATIC_REQUIRE_FALSE(...)                   \\\n    static_assert(!(__VA_ARGS__), \"!(\" #__VA_ARGS__ \")\"); \\\n    CATCH_SUCCEED(#__VA_ARGS__)\n#else\n#define CATCH_STATIC_REQUIRE(...) CATCH_REQUIRE(__VA_ARGS__)\n#define CATCH_STATIC_REQUIRE_FALSE(...) CATCH_REQUIRE_FALSE(__VA_ARGS__)\n#endif\n\n// \"BDD-style\" convenience wrappers\n#define CATCH_SCENARIO(...) CATCH_TEST_CASE(\"Scenario: \" __VA_ARGS__)\n#define CATCH_SCENARIO_METHOD(className, ...) \\\n    INTERNAL_CATCH_TEST_CASE_METHOD(className, \"Scenario: \" __VA_ARGS__)\n#define CATCH_GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(\"    Given: \" << desc)\n#define CATCH_AND_GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(\"And given: \" << desc)\n#define CATCH_WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(\"     When: \" << desc)\n#define CATCH_AND_WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(\" And when: \" << desc)\n#define CATCH_THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(\"     Then: \" << desc)\n#define CATCH_AND_THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(\"      And: \" << desc)\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n#define CATCH_BENCHMARK(...)                                                             \\\n    INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), \\\n                             INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__, , ),                  \\\n                             INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__, , ))\n#define CATCH_BENCHMARK_ADVANCED(name)                                                            \\\n    INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), \\\n                                      name)\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not\n// required\n#else\n\n#define REQUIRE(...) INTERNAL_CATCH_TEST(\"REQUIRE\", Catch::ResultDisposition::Normal, __VA_ARGS__)\n#define REQUIRE_FALSE(...)                                                                      \\\n    INTERNAL_CATCH_TEST(\"REQUIRE_FALSE\",                                                        \\\n                        Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, \\\n                        __VA_ARGS__)\n\n#define REQUIRE_THROWS(...) \\\n    INTERNAL_CATCH_THROWS(\"REQUIRE_THROWS\", Catch::ResultDisposition::Normal, __VA_ARGS__)\n#define REQUIRE_THROWS_AS(expr, exceptionType)                                                     \\\n    INTERNAL_CATCH_THROWS_AS(\"REQUIRE_THROWS_AS\", exceptionType, Catch::ResultDisposition::Normal, \\\n                             expr)\n#define REQUIRE_THROWS_WITH(expr, matcher)                                                     \\\n    INTERNAL_CATCH_THROWS_STR_MATCHES(\"REQUIRE_THROWS_WITH\", Catch::ResultDisposition::Normal, \\\n                                      matcher, expr)\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher)               \\\n    INTERNAL_CATCH_THROWS_MATCHES(\"REQUIRE_THROWS_MATCHES\", exceptionType, \\\n                                  Catch::ResultDisposition::Normal, matcher, expr)\n#endif  // CATCH_CONFIG_DISABLE_MATCHERS\n#define REQUIRE_NOTHROW(...) \\\n    INTERNAL_CATCH_NO_THROW(\"REQUIRE_NOTHROW\", Catch::ResultDisposition::Normal, __VA_ARGS__)\n\n#define CHECK(...) \\\n    INTERNAL_CATCH_TEST(\"CHECK\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__)\n#define CHECK_FALSE(...)                                                                       \\\n    INTERNAL_CATCH_TEST(                                                                       \\\n            \"CHECK_FALSE\",                                                                     \\\n            Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, \\\n            __VA_ARGS__)\n#define CHECKED_IF(...) \\\n    INTERNAL_CATCH_IF(\"CHECKED_IF\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__)\n#define CHECKED_ELSE(...) \\\n    INTERNAL_CATCH_ELSE(\"CHECKED_ELSE\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__)\n#define CHECK_NOFAIL(...)                                                                         \\\n    INTERNAL_CATCH_TEST(                                                                          \\\n            \"CHECK_NOFAIL\",                                                                       \\\n            Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, \\\n            __VA_ARGS__)\n\n#define CHECK_THROWS(...) \\\n    INTERNAL_CATCH_THROWS(\"CHECK_THROWS\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__)\n#define CHECK_THROWS_AS(expr, exceptionType)                   \\\n    INTERNAL_CATCH_THROWS_AS(\"CHECK_THROWS_AS\", exceptionType, \\\n                             Catch::ResultDisposition::ContinueOnFailure, expr)\n#define CHECK_THROWS_WITH(expr, matcher)                   \\\n    INTERNAL_CATCH_THROWS_STR_MATCHES(\"CHECK_THROWS_WITH\", \\\n                                      Catch::ResultDisposition::ContinueOnFailure, matcher, expr)\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CHECK_THROWS_MATCHES(expr, exceptionType, matcher)               \\\n    INTERNAL_CATCH_THROWS_MATCHES(\"CHECK_THROWS_MATCHES\", exceptionType, \\\n                                  Catch::ResultDisposition::ContinueOnFailure, matcher, expr)\n#endif  // CATCH_CONFIG_DISABLE_MATCHERS\n#define CHECK_NOTHROW(...)                                                                \\\n    INTERNAL_CATCH_NO_THROW(\"CHECK_NOTHROW\", Catch::ResultDisposition::ContinueOnFailure, \\\n                            __VA_ARGS__)\n\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CHECK_THAT(arg, matcher) \\\n    INTERNAL_CHECK_THAT(\"CHECK_THAT\", matcher, Catch::ResultDisposition::ContinueOnFailure, arg)\n\n#define REQUIRE_THAT(arg, matcher) \\\n    INTERNAL_CHECK_THAT(\"REQUIRE_THAT\", matcher, Catch::ResultDisposition::Normal, arg)\n#endif  // CATCH_CONFIG_DISABLE_MATCHERS\n\n#define INFO(msg) INTERNAL_CATCH_INFO(\"INFO\", msg)\n#define UNSCOPED_INFO(msg) INTERNAL_CATCH_UNSCOPED_INFO(\"UNSCOPED_INFO\", msg)\n#define WARN(msg)                                         \\\n    INTERNAL_CATCH_MSG(\"WARN\", Catch::ResultWas::Warning, \\\n                       Catch::ResultDisposition::ContinueOnFailure, msg)\n#define CAPTURE(...) \\\n    INTERNAL_CATCH_CAPTURE(INTERNAL_CATCH_UNIQUE_NAME(capturer), \"CAPTURE\", __VA_ARGS__)\n\n#define TEST_CASE(...) INTERNAL_CATCH_TESTCASE(__VA_ARGS__)\n#define TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEST_CASE_METHOD(className, __VA_ARGS__)\n#define METHOD_AS_TEST_CASE(method, ...) INTERNAL_CATCH_METHOD_AS_TEST_CASE(method, __VA_ARGS__)\n#define REGISTER_TEST_CASE(Function, ...) INTERNAL_CATCH_REGISTER_TESTCASE(Function, __VA_ARGS__)\n#define SECTION(...) INTERNAL_CATCH_SECTION(__VA_ARGS__)\n#define DYNAMIC_SECTION(...) INTERNAL_CATCH_DYNAMIC_SECTION(__VA_ARGS__)\n#define FAIL(...)                                                 \\\n    INTERNAL_CATCH_MSG(\"FAIL\", Catch::ResultWas::ExplicitFailure, \\\n                       Catch::ResultDisposition::Normal, __VA_ARGS__)\n#define FAIL_CHECK(...)                                                 \\\n    INTERNAL_CATCH_MSG(\"FAIL_CHECK\", Catch::ResultWas::ExplicitFailure, \\\n                       Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__)\n#define SUCCEED(...)                                    \\\n    INTERNAL_CATCH_MSG(\"SUCCEED\", Catch::ResultWas::Ok, \\\n                       Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__)\n#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE()\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__)\n#define TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(__VA_ARGS__)\n#define TEMPLATE_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__)\n#define TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, __VA_ARGS__)\n#define TEMPLATE_PRODUCT_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(__VA_ARGS__)\n#define TEMPLATE_PRODUCT_TEST_CASE_SIG(...) \\\n    INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(__VA_ARGS__)\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, __VA_ARGS__)\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) \\\n    INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, __VA_ARGS__)\n#define TEMPLATE_LIST_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__)\n#define TEMPLATE_LIST_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(className, __VA_ARGS__)\n#else\n#define TEMPLATE_TEST_CASE(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__))\n#define TEMPLATE_TEST_CASE_SIG(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(__VA_ARGS__))\n#define TEMPLATE_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__))\n#define TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(                      \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, __VA_ARGS__))\n#define TEMPLATE_PRODUCT_TEST_CASE(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(__VA_ARGS__))\n#define TEMPLATE_PRODUCT_TEST_CASE_SIG(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(__VA_ARGS__))\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(                          \\\n            INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, __VA_ARGS__))\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(                              \\\n            INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, __VA_ARGS__))\n#define TEMPLATE_LIST_TEST_CASE(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__))\n#define TEMPLATE_LIST_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(                       \\\n            INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(className, __VA_ARGS__))\n#endif\n\n#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)\n#define STATIC_REQUIRE(...)                   \\\n    static_assert(__VA_ARGS__, #__VA_ARGS__); \\\n    SUCCEED(#__VA_ARGS__)\n#define STATIC_REQUIRE_FALSE(...)                         \\\n    static_assert(!(__VA_ARGS__), \"!(\" #__VA_ARGS__ \")\"); \\\n    SUCCEED(\"!(\" #__VA_ARGS__ \")\")\n#else\n#define STATIC_REQUIRE(...) REQUIRE(__VA_ARGS__)\n#define STATIC_REQUIRE_FALSE(...) REQUIRE_FALSE(__VA_ARGS__)\n#endif\n\n#endif\n\n#define CATCH_TRANSLATE_EXCEPTION(signature) INTERNAL_CATCH_TRANSLATE_EXCEPTION(signature)\n\n// \"BDD-style\" convenience wrappers\n#define SCENARIO(...) TEST_CASE(\"Scenario: \" __VA_ARGS__)\n#define SCENARIO_METHOD(className, ...) \\\n    INTERNAL_CATCH_TEST_CASE_METHOD(className, \"Scenario: \" __VA_ARGS__)\n\n#define GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(\"    Given: \" << desc)\n#define AND_GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(\"And given: \" << desc)\n#define WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(\"     When: \" << desc)\n#define AND_WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(\" And when: \" << desc)\n#define THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(\"     Then: \" << desc)\n#define AND_THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(\"      And: \" << desc)\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n#define BENCHMARK(...)                                                                   \\\n    INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), \\\n                             INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__, , ),                  \\\n                             INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__, , ))\n#define BENCHMARK_ADVANCED(name)                                                                  \\\n    INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), \\\n                                      name)\n#endif  // CATCH_CONFIG_ENABLE_BENCHMARKING\n\nusing Catch::Detail::Approx;\n\n#else  // CATCH_CONFIG_DISABLE\n\n//////\n// If this config identifier is defined then all CATCH macros are prefixed with\n// CATCH_\n#ifdef CATCH_CONFIG_PREFIX_ALL\n\n#define CATCH_REQUIRE(...) (void)(0)\n#define CATCH_REQUIRE_FALSE(...) (void)(0)\n\n#define CATCH_REQUIRE_THROWS(...) (void)(0)\n#define CATCH_REQUIRE_THROWS_AS(expr, exceptionType) (void)(0)\n#define CATCH_REQUIRE_THROWS_WITH(expr, matcher) (void)(0)\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0)\n#endif  // CATCH_CONFIG_DISABLE_MATCHERS\n#define CATCH_REQUIRE_NOTHROW(...) (void)(0)\n\n#define CATCH_CHECK(...) (void)(0)\n#define CATCH_CHECK_FALSE(...) (void)(0)\n#define CATCH_CHECKED_IF(...) if (__VA_ARGS__)\n#define CATCH_CHECKED_ELSE(...) if (!(__VA_ARGS__))\n#define CATCH_CHECK_NOFAIL(...) (void)(0)\n\n#define CATCH_CHECK_THROWS(...) (void)(0)\n#define CATCH_CHECK_THROWS_AS(expr, exceptionType) (void)(0)\n#define CATCH_CHECK_THROWS_WITH(expr, matcher) (void)(0)\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_CHECK_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0)\n#endif  // CATCH_CONFIG_DISABLE_MATCHERS\n#define CATCH_CHECK_NOTHROW(...) (void)(0)\n\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_CHECK_THAT(arg, matcher) (void)(0)\n\n#define CATCH_REQUIRE_THAT(arg, matcher) (void)(0)\n#endif  // CATCH_CONFIG_DISABLE_MATCHERS\n\n#define CATCH_INFO(msg) (void)(0)\n#define CATCH_UNSCOPED_INFO(msg) (void)(0)\n#define CATCH_WARN(msg) (void)(0)\n#define CATCH_CAPTURE(msg) (void)(0)\n\n#define CATCH_TEST_CASE(...)                 \\\n    INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____))\n#define CATCH_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(   \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____))\n#define CATCH_METHOD_AS_TEST_CASE(method, ...)\n#define CATCH_REGISTER_TEST_CASE(Function, ...) (void)(0)\n#define CATCH_SECTION(...)\n#define CATCH_DYNAMIC_SECTION(...)\n#define CATCH_FAIL(...) (void)(0)\n#define CATCH_FAIL_CHECK(...) (void)(0)\n#define CATCH_SUCCEED(...) (void)(0)\n\n#define CATCH_ANON_TEST_CASE()               \\\n    INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____))\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define CATCH_TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__)\n#define CATCH_TEMPLATE_TEST_CASE_SIG(...) \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__)\n#define CATCH_TEMPLATE_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__)\n#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__)\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE(...) CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__)\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(...) CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__)\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) \\\n    CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__)\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) \\\n    CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__)\n#else\n#define CATCH_TEMPLATE_TEST_CASE(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__))\n#define CATCH_TEMPLATE_TEST_CASE_SIG(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__))\n#define CATCH_TEMPLATE_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(                        \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__))\n#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(                            \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__))\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE(...) CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__)\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(...) CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__)\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) \\\n    CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__)\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) \\\n    CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__)\n#endif\n\n// \"BDD-style\" convenience wrappers\n#define CATCH_SCENARIO(...)                  \\\n    INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____))\n#define CATCH_SCENARIO_METHOD(className, ...)       \\\n    INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____), className)\n#define CATCH_GIVEN(desc)\n#define CATCH_AND_GIVEN(desc)\n#define CATCH_WHEN(desc)\n#define CATCH_AND_WHEN(desc)\n#define CATCH_THEN(desc)\n#define CATCH_AND_THEN(desc)\n\n#define CATCH_STATIC_REQUIRE(...) (void)(0)\n#define CATCH_STATIC_REQUIRE_FALSE(...) (void)(0)\n\n// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not\n// required\n#else\n\n#define REQUIRE(...) (void)(0)\n#define REQUIRE_FALSE(...) (void)(0)\n\n#define REQUIRE_THROWS(...) (void)(0)\n#define REQUIRE_THROWS_AS(expr, exceptionType) (void)(0)\n#define REQUIRE_THROWS_WITH(expr, matcher) (void)(0)\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0)\n#endif  // CATCH_CONFIG_DISABLE_MATCHERS\n#define REQUIRE_NOTHROW(...) (void)(0)\n\n#define CHECK(...) (void)(0)\n#define CHECK_FALSE(...) (void)(0)\n#define CHECKED_IF(...) if (__VA_ARGS__)\n#define CHECKED_ELSE(...) if (!(__VA_ARGS__))\n#define CHECK_NOFAIL(...) (void)(0)\n\n#define CHECK_THROWS(...) (void)(0)\n#define CHECK_THROWS_AS(expr, exceptionType) (void)(0)\n#define CHECK_THROWS_WITH(expr, matcher) (void)(0)\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CHECK_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0)\n#endif  // CATCH_CONFIG_DISABLE_MATCHERS\n#define CHECK_NOTHROW(...) (void)(0)\n\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CHECK_THAT(arg, matcher) (void)(0)\n\n#define REQUIRE_THAT(arg, matcher) (void)(0)\n#endif  // CATCH_CONFIG_DISABLE_MATCHERS\n\n#define INFO(msg) (void)(0)\n#define UNSCOPED_INFO(msg) (void)(0)\n#define WARN(msg) (void)(0)\n#define CAPTURE(msg) (void)(0)\n\n#define TEST_CASE(...)                       \\\n    INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____))\n#define TEST_CASE_METHOD(className, ...)     \\\n    INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____))\n#define METHOD_AS_TEST_CASE(method, ...)\n#define REGISTER_TEST_CASE(Function, ...) (void)(0)\n#define SECTION(...)\n#define DYNAMIC_SECTION(...)\n#define FAIL(...) (void)(0)\n#define FAIL_CHECK(...) (void)(0)\n#define SUCCEED(...) (void)(0)\n#define ANON_TEST_CASE()                     \\\n    INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____))\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__)\n#define TEMPLATE_TEST_CASE_SIG(...) \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__)\n#define TEMPLATE_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__)\n#define TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \\\n    INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__)\n#define TEMPLATE_PRODUCT_TEST_CASE(...) TEMPLATE_TEST_CASE(__VA_ARGS__)\n#define TEMPLATE_PRODUCT_TEST_CASE_SIG(...) TEMPLATE_TEST_CASE(__VA_ARGS__)\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) \\\n    TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__)\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) \\\n    TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__)\n#else\n#define TEMPLATE_TEST_CASE(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__))\n#define TEMPLATE_TEST_CASE_SIG(...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__))\n#define TEMPLATE_TEST_CASE_METHOD(className, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(                  \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__))\n#define TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \\\n    INTERNAL_CATCH_EXPAND_VARGS(                      \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__))\n#define TEMPLATE_PRODUCT_TEST_CASE(...) TEMPLATE_TEST_CASE(__VA_ARGS__)\n#define TEMPLATE_PRODUCT_TEST_CASE_SIG(...) TEMPLATE_TEST_CASE(__VA_ARGS__)\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) \\\n    TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__)\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) \\\n    TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__)\n#endif\n\n#define STATIC_REQUIRE(...) (void)(0)\n#define STATIC_REQUIRE_FALSE(...) (void)(0)\n\n#endif\n\n#define CATCH_TRANSLATE_EXCEPTION(signature)   \\\n    INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( \\\n            INTERNAL_CATCH_UNIQUE_NAME(catch_internal_ExceptionTranslator), signature)\n\n// \"BDD-style\" convenience wrappers\n#define SCENARIO(...)                        \\\n    INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____))\n#define SCENARIO_METHOD(className, ...)             \\\n    INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( \\\n            INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____), className)\n\n#define GIVEN(desc)\n#define AND_GIVEN(desc)\n#define WHEN(desc)\n#define AND_WHEN(desc)\n#define THEN(desc)\n#define AND_THEN(desc)\n\nusing Catch::Detail::Approx;\n\n#endif\n\n#endif  // ! CATCH_CONFIG_IMPL_ONLY\n\n// start catch_reenable_warnings.h\n\n#ifdef __clang__\n#ifdef __ICC  // icpc defines the __clang__ macro\n#pragma warning(pop)\n#else\n#pragma clang diagnostic pop\n#endif\n#elif defined __GNUC__\n#pragma GCC diagnostic pop\n#endif\n\n// end catch_reenable_warnings.h\n// end catch.hpp\n#endif  // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n"
  },
  {
    "path": "third_party/include/gsl/gsl",
    "content": "//\n// gsl-lite is based on GSL: Guidelines Support Library.\n// For more information see https://github.com/martinmoene/gsl-lite\n//\n// Copyright (c) 2015 Martin Moene\n// Copyright (c) 2015 Microsoft Corporation. All rights reserved.\n//\n// This code is licensed under the MIT License (MIT).\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n// THE SOFTWARE.\n\n// mimic MS include hierarchy\n\n#pragma once\n\n#ifndef GSL_GSL_H_INCLUDED\n#define GSL_GSL_H_INCLUDED\n\n#pragma message (\"gsl/gsl is deprecated since version 0.38.1, use gsl/gsl-lite.hpp instead.\")\n\n#include \"gsl-lite.hpp\"\n\n#endif // GSL_GSL_H_INCLUDED\n"
  },
  {
    "path": "third_party/include/gsl/gsl-lite-vc6.hpp",
    "content": "//\n// gsl-lite-vc6 is based on GSL: Guidelines Support Library,\n// For more information see https://github.com/gsl-lite/gsl-lite\n//\n// Copyright (c) 2015 Martin Moene\n// Copyright (c) 2015 Microsoft Corporation. All rights reserved.\n//\n// This code is licensed under the MIT License (MIT).\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n// THE SOFTWARE.\n\n#pragma once\n\n#ifndef GSL_GSL_LITE_H_INCLUDED\n#define GSL_GSL_LITE_H_INCLUDED\n\n#include <exception>\n#include <iterator>\n#include <limits>\n#include <memory>\n#include <stdexcept>\n#include <string>\n#include <utility>\n#include <vector>\n\n#define gsl_lite_VERSION \"0.0.0\"\n\n// Configuration:\n\n#ifndef gsl_FEATURE_IMPLICIT_MACRO\n#define gsl_FEATURE_IMPLICIT_MACRO 1\n#endif\n\n#ifndef gsl_FEATURE_OWNER_MACRO\n#define gsl_FEATURE_OWNER_MACRO 1\n#endif\n\n#ifndef gsl_FEATURE_SHARED_PTR\n#define gsl_FEATURE_SHARED_PTR 0\n#endif\n\n#ifndef gsl_FEATURE_UNIQUE_PTR\n#define gsl_FEATURE_UNIQUE_PTR 0\n#endif\n\n#ifndef gsl_CONFIG_THROWS_FOR_TESTING\n#define gsl_CONFIG_THROWS_FOR_TESTING 0\n#endif\n\n#ifndef gsl_CONFIG_CONFIRMS_COMPILATION_ERRORS\n#define gsl_CONFIG_CONFIRMS_COMPILATION_ERRORS 0\n#endif\n\n#ifndef gsl_CONFIG_SHARED_PTR_INCLUDE\n#define gsl_CONFIG_SHARED_PTR_INCLUDE <boost/shared_ptr.hpp>\n#endif\n\n#ifndef gsl_CONFIG_UNIQUE_PTR_INCLUDE\n#define gsl_CONFIG_UNIQUE_PTR_INCLUDE <boost/unique_ptr.hpp>\n#endif\n\n#ifndef gsl_CONFIG_SHARED_PTR_DECL\n#define gsl_CONFIG_SHARED_PTR_DECL boost::shared_ptr\n#endif\n\n#ifndef gsl_CONFIG_UNIQUE_PTR_DECL\n#define gsl_CONFIG_UNIQUE_PTR_DECL boost::unique_ptr\n#endif\n\n// Compiler detection:\n\n#if defined(_MSC_VER) && !defined(__clang__)\n#define gsl_COMPILER_MSVC_VER (_MSC_VER)\n#define gsl_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * (5 + (_MSC_VER < 1900)))\n#else\n#define gsl_COMPILER_MSVC_VER 0\n#define gsl_COMPILER_MSVC_VERSION 0\n#define gsl_COMPILER_NON_MSVC 1\n#endif\n\n#if gsl_COMPILER_MSVC_VERSION != 60\n#error GSL Lite: this header is for Visual C++ 6\n#endif\n\n// half-open range [lo..hi):\n#define gsl_BETWEEN(v, lo, hi) ((lo) <= (v) && (v) < (hi))\n\n// Presence of C++ language features:\n\n// C++ feature usage:\n\n#if gsl_FEATURE_IMPLICIT_MACRO\n#define implicit\n#endif\n\n#define gsl_DIMENSION_OF(a) (sizeof(a) / sizeof(0 [a]))\n\n#if gsl_FEATURE_SHARED_PTR\n#include gsl_CONFIG_SHARED_PTR_INCLUDE\n#endif\n\n#if gsl_FEATURE_UNIQUE_PTR\n#include gsl_CONFIG_UNIQUE_PTR_INCLUDE\n#endif\n\nnamespace gsl {\n\n//\n// GSL.owner: ownership pointers\n//\n// ToDo:\n#if gsl_FEATURE_SHARED_PTR\nusing gsl_CONFIG_SHARED_PTR_DECL;\n#endif\n#if gsl_FEATURE_UNIQUE_PTR\nusing gsl_CONFIG_UNIQUE_PTR_DECL;\n#endif\n\ntemplate <class T>\nstruct owner {\n    typedef T type;\n};\n\n#define gsl_HAVE_OWNER_TEMPLATE 0\n\n#if gsl_FEATURE_OWNER_MACRO\n#define Owner(t) ::gsl::owner<t>::type\n#endif\n\n//\n// GSL.assert: assertions\n//\n#define Expects(x) ::gsl::fail_fast_assert((x))\n#define Ensures(x) ::gsl::fail_fast_assert((x))\n\n#if gsl_CONFIG_THROWS_FOR_TESTING\n\nstruct fail_fast : public std::runtime_error {\n    fail_fast() : std::runtime_error(\"GSL assertion\") {}\n\n    explicit fail_fast(char const *const message) : std::runtime_error(message) {}\n};\n\ninline void fail_fast_assert(bool cond) {\n    if (!cond)\n        throw fail_fast();\n}\n\ninline void fail_fast_assert(bool cond, char const *const message) {\n    if (!cond)\n        throw fail_fast(message);\n}\n\n#else  // gsl_CONFIG_THROWS_FOR_TESTING\n\ninline void fail_fast_assert(bool cond) {\n    if (!cond)\n        terminate();\n}\n\ninline void fail_fast_assert(bool cond, char const *const) {\n    if (!cond)\n        terminate();\n}\n\n#endif  // gsl_CONFIG_THROWS_FOR_TESTING\n\n//\n// GSL.util: utilities\n//\n\nclass final_action {\npublic:\n    typedef void (*Action)();\n\n    final_action(Action action) : action_(action) {}\n\n    ~final_action() { action_(); }\n\nprivate:\n    Action action_;\n};\n\ntemplate <class Fn>\nfinal_action finally(Fn const &f) {\n    return final_action((f));\n}\n\ntemplate <class T, class U>\nT narrow_cast(U u) {\n    return static_cast<T>(u);\n}\n\nstruct narrowing_error : public std::exception {};\n\ntemplate <class T, class U>\nT narrow(U u) {\n    T t = narrow_cast<T>(u);\n\n    if (static_cast<U>(t) != u) {\n        throw narrowing_error();\n    }\n    return t;\n}\n\n//\n// GSL.views: views\n//\n\n//\n// at() - Bounds-checked way of accessing static arrays, std::array,\n// std::vector.\n//\n\nnamespace detail {\n\nstruct precedence_0 {};\nstruct precedence_1 : precedence_0 {};\nstruct order_precedence : precedence_1 {};\n\ntemplate <class Array, class T>\nT &at(Array &arr, size_t index, T *, precedence_0 const &) {\n    Expects(index < gsl_DIMENSION_OF(arr));\n    return arr[index];\n}\n\n}  // namespace detail\n\n// Create an at( container ) function:\n\n#define gsl_MK_AT(Cont)                                                    \\\n    namespace gsl {                                                        \\\n    namespace detail {                                                     \\\n    template <class T>                                                     \\\n    inline T &at(Cont<T> &cont, size_t index, T *, precedence_1 const &) { \\\n        Expects(index < cont.size());                                      \\\n        return cont[index];                                                \\\n    }                                                                      \\\n    }                                                                      \\\n    }\n\ntemplate <class Cont>\nint &at(Cont &cont, size_t index) {\n    return detail::at(cont, index, &cont[0], detail::order_precedence());\n}\n\n//\n// not_null<> - Wrap any indirection and enforce non-null.\n//\ntemplate <class T>\nclass not_null {\npublic:\n    not_null(T t) : ptr_(t) { Expects(ptr_ != NULL); }\n    not_null &operator=(T const &t) {\n        ptr_ = t;\n        Expects(ptr_ != NULL);\n        return *this;\n    }\n\n    not_null(not_null const &other) : ptr_(other.ptr_) {}\n    not_null &operator=(not_null const &other) { ptr_ = other.ptr_; }\n\n    // VC6 accepts this anyway:\n    // template< typename U > not_null( not_null<U> const & other );\n    // template< typename U > not_null & operator=( not_null<U> const & other ) ;\n\nprivate:\n    // Prevent compilation when initialized with a literal 0:\n    not_null(int);\n    not_null &operator=(int);\n\npublic:\n    T get() const { return ptr_; }\n\n    operator T() const { return get(); }\n    T operator->() const { return get(); }\n\n    bool operator==(T const &rhs) const { return ptr_ == rhs; }\n    bool operator!=(T const &rhs) const { return !(*this == rhs); }\n\nprivate:\n    T ptr_;\n\n    not_null &operator++();\n    not_null &operator--();\n    not_null operator++(int);\n    not_null operator--(int);\n    not_null &operator+(size_t);\n    not_null &operator+=(size_t);\n    not_null &operator-(size_t);\n    not_null &operator-=(size_t);\n};\n\n//\n// Byte-specific type.\n//\ntypedef unsigned char byte;\n\n//\n// span<> - A 1D view of contiguous T's, replace (*,len).\n//\ntemplate <class T>\nclass span {\npublic:\n    typedef size_t size_type;\n\n    typedef T value_type;\n    typedef T &reference;\n    typedef T *pointer;\n    typedef T const *const_pointer;\n\n    typedef pointer iterator;\n    typedef const_pointer const_iterator;\n\n    typedef std::reverse_iterator<iterator, T> reverse_iterator;\n    typedef std::reverse_iterator<const_iterator, const T> const_reverse_iterator;\n\n    // Todo:\n    // typedef typename std::iterator_traits< iterator >::difference_type\n    // difference_type;\n\n    span() : begin_(NULL), end_(NULL) { Expects(size() == 0); }\n\n    span(pointer begin, pointer end) : begin_(begin), end_(end) { Expects(begin <= end); }\n\n    span(pointer data, size_type size) : begin_(data), end_(data + size) {\n        Expects(size == 0 || (size > 0 && data != NULL));\n    }\n\nprivate:\n    struct precedence_0 {};\n    struct precedence_1 : precedence_0 {};\n    struct precedence_2 : precedence_1 {};\n    struct order_precedence : precedence_1 {};\n\n    template <class Array, class U>\n    span create(Array &arr, U *, precedence_0 const &) const {\n        return span(arr, gsl_DIMENSION_OF(arr));\n    }\n\n    span create(std::vector<T> &cont, T *, precedence_1 const &) const {\n        return span(&cont[0], cont.size());\n    }\n\npublic:\n    template <class Cont>\n    span(Cont &cont) {\n        *this = create(cont, &cont[0], order_precedence());\n    }\n\n#if 0\n    // =default constructor\n    span( span const & other )\n        : begin_( other.begin() )\n        , end_  ( other.end() )\n    {}\n#endif\n\n    span &operator=(span const &other) {\n        // VC6 balks at copy-swap implementation (here),\n        // so we do it the simple way:\n        begin_ = other.begin_;\n        end_ = other.end_;\n        return *this;\n    }\n\n#if 0\n    // Converting from other span ?\n    template< typename U > operator=();\n#endif\n\n    iterator begin() const { return iterator(begin_); }\n\n    iterator end() const { return iterator(end_); }\n\n    const_iterator cbegin() const { return const_iterator(begin()); }\n\n    const_iterator cend() const { return const_iterator(end()); }\n\n    reverse_iterator rbegin() const { return reverse_iterator(end()); }\n\n    reverse_iterator rend() const { return reverse_iterator(begin()); }\n\n    const_reverse_iterator crbegin() const { return const_reverse_iterator(cend()); }\n\n    const_reverse_iterator crend() const { return const_reverse_iterator(cbegin()); }\n\n    operator bool() const { return begin_ != NULL; }\n\n    reference operator[](size_type index) { return at(index); }\n\n    bool operator==(span const &other) const {\n        return size() == other.size() &&\n               (begin_ == other.begin_ || std::equal(this->begin(), this->end(), other.begin()));\n    }\n\n    bool operator!=(span const &other) const { return !(*this == other); }\n\n    bool operator<(span const &other) const {\n        return std::lexicographical_compare(this->begin(), this->end(), other.begin(), other.end());\n    }\n\n    bool operator<=(span const &other) const { return !(other < *this); }\n\n    bool operator>(span const &other) const { return (other < *this); }\n\n    bool operator>=(span const &other) const { return !(*this < other); }\n\n    reference at(size_type index) {\n        Expects(index >= 0 && index < size());\n        return begin_[index];\n    }\n\n    pointer data() const { return begin_; }\n\n    bool empty() const { return size() == 0; }\n\n    size_type size() const { return std::distance(begin_, end_); }\n\n    size_type length() const { return size(); }\n\n    size_type used_length() const { return length(); }\n\n    size_type bytes() const { return sizeof(value_type) * size(); }\n\n    size_type used_bytes() const { return bytes(); }\n\n    void swap(span &other) {\n        using std::swap;\n        swap(begin_, other.begin_);\n        swap(end_, other.end_);\n    }\n\n    span<const byte> as_bytes() const {\n        return span<const byte>(reinterpret_cast<const byte *>(data()), bytes());\n    }\n\n    span<byte> as_writeable_bytes() const {\n        return span<byte>(reinterpret_cast<byte *>(data()), bytes());\n    }\n\n    template <class U>\n    struct mk {\n        static span<U> view(U *data, size_type size) { return span<U>(data, size); }\n    };\n\n    template <typename U>\n    span<U> as_span(U u = U()) const {\n        Expects((this->bytes() % sizeof(U)) == 0);\n        return mk<U>::view(reinterpret_cast<U *>(this->data()), this->bytes() / sizeof(U));\n    }\n\nprivate:\n    pointer begin_;\n    pointer end_;\n};\n\n// span creator functions (see ctors)\n\ntemplate <typename T>\nspan<const byte> as_bytes(span<T> spn) {\n    return span<const byte>(reinterpret_cast<const byte *>(spn.data()), spn.bytes());\n}\n\ntemplate <typename T>\nspan<byte> as_writeable_bytes(span<T> spn) {\n    return span<byte>(reinterpret_cast<byte *>(spn.data()), spn.bytes());\n}\n\ntemplate <typename T>\nspan<T> as_span(T *begin, T *end) {\n    return span<T>(begin, end);\n}\n\ntemplate <typename T>\nspan<T> as_span(T *begin, size_t size) {\n    return span<T>(begin, size);\n}\n\nnamespace detail {\n\ntemplate <class T>\nstruct mk {\n    static span<T> view(std::vector<T> &cont) { return span<T>(cont); }\n};\n}  // namespace detail\n\ntemplate <class T>\nspan<T> as_span(std::vector<T> &cont) {\n    return detail::mk<T>::view(cont);\n}\n\n//\n// String types:\n//\n\ntypedef char *zstring;\ntypedef wchar_t *zwstring;\ntypedef const char *czstring;\ntypedef const wchar_t *cwzstring;\n\ntypedef span<char> string_span;\ntypedef span<wchar_t> wstring_span;\ntypedef span<const char> cstring_span;\ntypedef span<const wchar_t> cwstring_span;\n\n// to_string() allow (explicit) conversions from string_span to string\n\ninline std::string to_string(string_span const &view) {\n    return std::string(view.data(), view.length());\n}\n\ninline std::string to_string(cstring_span const &view) {\n    return std::string(view.data(), view.length());\n}\n\ninline std::wstring to_string(wstring_span const &view) {\n    return std::wstring(view.data(), view.length());\n}\n\ninline std::wstring to_string(cwstring_span const &view) {\n    return std::wstring(view.data(), view.length());\n}\n\n//\n// ensure_sentinel()\n//\n// Provides a way to obtain a span from a contiguous sequence\n// that ends with a (non-inclusive) sentinel value.\n//\n// Will fail-fast if sentinel cannot be found before max elements are examined.\n//\nnamespace detail {\n\ntemplate <class T, class SizeType, const T Sentinel>\nstruct ensure {\n    static span<T> sentinel(T *seq, SizeType max = (std::numeric_limits<SizeType>::max)()) {\n        typedef T *pointer;\n        typedef typename std::iterator_traits<pointer>::difference_type difference_type;\n\n        pointer cur = seq;\n\n        while (std::distance(seq, cur) < static_cast<difference_type>(max) && *cur != Sentinel)\n            ++cur;\n\n        Expects(*cur == Sentinel);\n\n        return span<T>(seq, cur - seq);\n    }\n};\n}  // namespace detail\n\n//\n// ensure_z - creates a string_span for a czstring or cwzstring.\n// Will fail fast if a null-terminator cannot be found before\n// the limit of size_type.\n//\n\ntemplate <typename T>\nspan<T> ensure_z(T *sz, size_t max = (std::numeric_limits<size_t>::max)()) {\n    return detail::ensure<T, size_t, 0>::sentinel(sz, max);\n}\n\n}  // namespace gsl\n\n// at( std::vector ):\n\ngsl_MK_AT(std::vector)\n\n#endif  // GSL_GSL_LITE_H_INCLUDED\n\n        // end of file\n"
  },
  {
    "path": "third_party/include/gsl/gsl-lite.h",
    "content": "//\n// gsl-lite is based on GSL: Guidelines Support Library.\n// For more information see https://github.com/gsl-lite/gsl-lite\n//\n// Copyright (c) 2015 Martin Moene\n// Copyright (c) 2015 Microsoft Corporation. All rights reserved.\n//\n// This code is licensed under the MIT License (MIT).\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n// THE SOFTWARE.\n\n// mimic MS include hierarchy\n\n#ifndef GSL_GSL_LITE_H_INCLUDED\n#define GSL_GSL_LITE_H_INCLUDED\n\n#pragma message(\"gsl/gsl-lite.h is deprecated since version 0.27.0, use gsl/gsl-lite.hpp instead.\")\n\n#include \"gsl-lite.hpp\"\n\n#endif  // GSL_GSL_LITE_H_INCLUDED\n"
  },
  {
    "path": "third_party/include/gsl/gsl-lite.hpp",
    "content": "//\n// gsl-lite is based on GSL: Guidelines Support Library.\n// For more information see https://github.com/gsl-lite/gsl-lite\n//\n// Copyright (c) 2015-2019 Martin Moene\n// Copyright (c) 2019-2021 Moritz Beutel\n// Copyright (c) 2015-2018 Microsoft Corporation. All rights reserved.\n//\n// This code is licensed under the MIT License (MIT).\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n// THE SOFTWARE.\n\n#ifndef GSL_GSL_LITE_HPP_INCLUDED\n#define GSL_GSL_LITE_HPP_INCLUDED\n\n#include <cstddef>    // for size_t, ptrdiff_t, nullptr_t\n#include <exception>  // for exception, terminate(), uncaught_exceptions()\n#include <ios>        // for ios_base, streamsize\n#include <iosfwd>     // for basic_ostream<>\n#include <limits>\n#include <memory>     // for addressof(), unique_ptr<>, shared_ptr<>\n#include <stdexcept>  // for logic_error\n#include <string>\n#include <utility>  // for move(), forward<>(), swap()\n\n#define gsl_lite_MAJOR 0\n#define gsl_lite_MINOR 38\n#define gsl_lite_PATCH 1\n\n#define gsl_lite_VERSION                                                               \\\n    gsl_STRINGIFY(gsl_lite_MAJOR) \".\" gsl_STRINGIFY(gsl_lite_MINOR) \".\" gsl_STRINGIFY( \\\n            gsl_lite_PATCH)\n\n#define gsl_STRINGIFY(x) gsl_STRINGIFY_(x)\n#define gsl_STRINGIFY_(x) #x\n#define gsl_CONCAT_(a, b) gsl_CONCAT2_(a, b)\n#define gsl_CONCAT2_(a, b) a##b\n#define gsl_EVALF_(f) f()\n\n// configuration argument checking:\n\n#define gsl_DETAIL_CFG_TOGGLE_VALUE_1 1\n#define gsl_DETAIL_CFG_TOGGLE_VALUE_0 1\n#define gsl_DETAIL_CFG_DEFAULTS_VERSION_VALUE_1 1\n#define gsl_DETAIL_CFG_DEFAULTS_VERSION_VALUE_0 1\n#define gsl_DETAIL_CFG_STD_VALUE_98 1\n#define gsl_DETAIL_CFG_STD_VALUE_3 1\n#define gsl_DETAIL_CFG_STD_VALUE_03 1\n#define gsl_DETAIL_CFG_STD_VALUE_11 1\n#define gsl_DETAIL_CFG_STD_VALUE_14 1\n#define gsl_DETAIL_CFG_STD_VALUE_17 1\n#define gsl_DETAIL_CFG_STD_VALUE_20 1\n#define gsl_DETAIL_CFG_NO_VALUE_ 1\n#define gsl_DETAIL_CFG_NO_VALUE_1 \\\n    1  // many compilers treat the command-line parameter \"-Dfoo\" as equivalent to \\\n            // \"-Dfoo=1\", so we tolerate that\n#define gsl_CHECK_CFG_TOGGLE_VALUE_(x) gsl_CONCAT_(gsl_DETAIL_CFG_TOGGLE_VALUE_, x)\n#define gsl_CHECK_CFG_DEFAULTS_VERSION_VALUE_(x) \\\n    gsl_CONCAT_(gsl_DETAIL_CFG_DEFAULTS_VERSION_VALUE_, x)\n#define gsl_CHECK_CFG_STD_VALUE_(x) gsl_CONCAT_(gsl_DETAIL_CFG_STD_VALUE_, x)\n#define gsl_CHECK_CFG_NO_VALUE_(x) gsl_CONCAT_(gsl_DETAIL_CFG_NO_VALUE, gsl_CONCAT_(_, x))\n\n// gsl-lite backward compatibility:\n\n#if defined(gsl_CONFIG_DEFAULTS_VERSION)\n#if !gsl_CHECK_CFG_DEFAULTS_VERSION_VALUE_(gsl_CONFIG_DEFAULTS_VERSION)\n#pragma message(\"invalid configuration value gsl_CONFIG_DEFAULTS_VERSION=\" gsl_STRINGIFY( \\\n        gsl_CONFIG_DEFAULTS_VERSION) \", must be 0 or 1\")\n#endif\n#else\n#define gsl_CONFIG_DEFAULTS_VERSION gsl_lite_MAJOR  // default\n#endif\n#define gsl_CONFIG_DEFAULTS_VERSION_() gsl_CONFIG_DEFAULTS_VERSION\n\n#if defined(gsl_CONFIG_ALLOWS_SPAN_CONTAINER_CTOR)\n#if !gsl_CHECK_CFG_TOGGLE_VALUE_(gsl_CONFIG_ALLOWS_SPAN_CONTAINER_CTOR)\n#pragma message(                                                                            \\\n        \"invalid configuration value gsl_CONFIG_ALLOWS_SPAN_CONTAINER_CTOR=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_ALLOWS_SPAN_CONTAINER_CTOR) \", must be 0 or 1\")\n#endif\n#define gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR gsl_CONFIG_ALLOWS_SPAN_CONTAINER_CTOR\n#pragma message( \\\n        \"gsl_CONFIG_ALLOWS_SPAN_CONTAINER_CTOR is deprecated since gsl-lite 0.7; replace with gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR, or consider span(with_container, cont).\")\n#endif\n\n#if defined(gsl_CONFIG_CONTRACT_LEVEL_ON)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_LEVEL_ON)\n#pragma message(\"invalid configuration value gsl_CONFIG_CONTRACT_LEVEL_ON=\" gsl_STRINGIFY( \\\n        gsl_CONFIG_CONTRACT_LEVEL_ON) \"; macro must be defined without value\")\n#endif\n#pragma message( \\\n        \"gsl_CONFIG_CONTRACT_LEVEL_ON is deprecated since gsl-lite 0.36; replace with gsl_CONFIG_CONTRACT_CHECKING_ON.\")\n#define gsl_CONFIG_CONTRACT_CHECKING_ON\n#endif\n#if defined(gsl_CONFIG_CONTRACT_LEVEL_OFF)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_LEVEL_OFF)\n#pragma message(\"invalid configuration value gsl_CONFIG_CONTRACT_LEVEL_OFF=\" gsl_STRINGIFY( \\\n        gsl_CONFIG_CONTRACT_LEVEL_OFF) \"; macro must be defined without value\")\n#endif\n#pragma message( \\\n        \"gsl_CONFIG_CONTRACT_LEVEL_OFF is deprecated since gsl-lite 0.36; replace with gsl_CONFIG_CONTRACT_CHECKING_OFF.\")\n#define gsl_CONFIG_CONTRACT_CHECKING_OFF\n#endif\n#if defined(gsl_CONFIG_CONTRACT_LEVEL_EXPECTS_ONLY)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_LEVEL_EXPECTS_ONLY)\n#pragma message(                                                                             \\\n        \"invalid configuration value gsl_CONFIG_CONTRACT_LEVEL_EXPECTS_ONLY=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_CONTRACT_LEVEL_EXPECTS_ONLY) \"; macro must be defined without value\")\n#endif\n#pragma message( \\\n        \"gsl_CONFIG_CONTRACT_LEVEL_EXPECTS_ONLY is deprecated since gsl-lite 0.36; replace with gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF and gsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF.\")\n#define gsl_CONFIG_CONTRACT_CHECKING_ON\n#define gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF\n#define gsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF\n#elif defined(gsl_CONFIG_CONTRACT_LEVEL_ENSURES_ONLY)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_LEVEL_ENSURES_ONLY)\n#pragma message(                                                                             \\\n        \"invalid configuration value gsl_CONFIG_CONTRACT_LEVEL_ENSURES_ONLY=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_CONTRACT_LEVEL_ENSURES_ONLY) \"; macro must be defined without value\")\n#endif\n#pragma message( \\\n        \"gsl_CONFIG_CONTRACT_LEVEL_ENSURES_ONLY is deprecated since gsl-lite 0.36; replace with gsl_CONFIG_CONTRACT_CHECKING_EXPECTS_OFF and gsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF.\")\n#define gsl_CONFIG_CONTRACT_CHECKING_ON\n#define gsl_CONFIG_CONTRACT_CHECKING_EXPECTS_OFF\n#define gsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF\n#endif\n\n// M-GSL compatibility:\n\n#if defined(GSL_THROW_ON_CONTRACT_VIOLATION)\n#if !gsl_CHECK_CFG_NO_VALUE_(GSL_THROW_ON_CONTRACT_VIOLATION)\n#pragma message(\"invalid configuration value GSL_THROW_ON_CONTRACT_VIOLATION=\" gsl_STRINGIFY( \\\n        GSL_THROW_ON_CONTRACT_VIOLATION) \"; macro must be defined without value\")\n#endif\n#define gsl_CONFIG_CONTRACT_VIOLATION_THROWS\n#endif\n\n#if defined(GSL_TERMINATE_ON_CONTRACT_VIOLATION)\n#if !gsl_CHECK_CFG_NO_VALUE_(GSL_TERMINATE_ON_CONTRACT_VIOLATION)\n#pragma message(\"invalid configuration value GSL_TERMINATE_ON_CONTRACT_VIOLATION=\" gsl_STRINGIFY( \\\n        GSL_TERMINATE_ON_CONTRACT_VIOLATION) \"; macro must be defined without value\")\n#endif\n#define gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES\n#endif\n\n#if defined(GSL_UNENFORCED_ON_CONTRACT_VIOLATION)\n#if !gsl_CHECK_CFG_NO_VALUE_(GSL_UNENFORCED_ON_CONTRACT_VIOLATION)\n#pragma message(\"invalid configuration value GSL_UNENFORCED_ON_CONTRACT_VIOLATION=\" gsl_STRINGIFY( \\\n        GSL_UNENFORCED_ON_CONTRACT_VIOLATION) \"; macro must be defined without value\")\n#endif\n#define gsl_CONFIG_CONTRACT_CHECKING_OFF\n#endif\n\n// Configuration: Features\n\n#if defined(gsl_FEATURE_WITH_CONTAINER_TO_STD)\n#if !gsl_CHECK_CFG_STD_VALUE_(gsl_FEATURE_WITH_CONTAINER_TO_STD)\n#pragma message(\"invalid configuration value gsl_FEATURE_WITH_CONTAINER_TO_STD=\" gsl_STRINGIFY( \\\n        gsl_FEATURE_WITH_CONTAINER_TO_STD) \", must be 98, 3, 11, 14, 17, or 20\")\n#endif\n#else\n#define gsl_FEATURE_WITH_CONTAINER_TO_STD 99  // default\n#endif\n#define gsl_FEATURE_WITH_CONTAINER_TO_STD_() gsl_FEATURE_WITH_CONTAINER_TO_STD\n\n#if defined(gsl_FEATURE_MAKE_SPAN_TO_STD)\n#if !gsl_CHECK_CFG_STD_VALUE_(gsl_FEATURE_MAKE_SPAN_TO_STD)\n#pragma message(\"invalid configuration value gsl_FEATURE_MAKE_SPAN_TO_STD=\" gsl_STRINGIFY( \\\n        gsl_FEATURE_MAKE_SPAN_TO_STD) \", must be 98, 3, 11, 14, 17, or 20\")\n#endif\n#else\n#define gsl_FEATURE_MAKE_SPAN_TO_STD 99  // default\n#endif\n#define gsl_FEATURE_MAKE_SPAN_TO_STD_() gsl_FEATURE_MAKE_SPAN_TO_STD\n\n#if defined(gsl_FEATURE_BYTE_SPAN_TO_STD)\n#if !gsl_CHECK_CFG_STD_VALUE_(gsl_FEATURE_BYTE_SPAN_TO_STD)\n#pragma message(\"invalid configuration value gsl_FEATURE_BYTE_SPAN_TO_STD=\" gsl_STRINGIFY( \\\n        gsl_FEATURE_BYTE_SPAN_TO_STD) \", must be 98, 3, 11, 14, 17, or 20\")\n#endif\n#else\n#define gsl_FEATURE_BYTE_SPAN_TO_STD 99  // default\n#endif\n#define gsl_FEATURE_BYTE_SPAN_TO_STD_() gsl_FEATURE_BYTE_SPAN_TO_STD\n\n#if defined(gsl_FEATURE_IMPLICIT_MACRO)\n#if !gsl_CHECK_CFG_TOGGLE_VALUE_(gsl_FEATURE_IMPLICIT_MACRO)\n#pragma message(\"invalid configuration value gsl_FEATURE_IMPLICIT_MACRO=\" gsl_STRINGIFY( \\\n        gsl_FEATURE_IMPLICIT_MACRO) \", must be 0 or 1\")\n#endif\n#else\n#define gsl_FEATURE_IMPLICIT_MACRO 0  // default\n#endif\n#define gsl_FEATURE_IMPLICIT_MACRO_() gsl_FEATURE_IMPLICIT_MACRO\n\n#if defined(gsl_FEATURE_OWNER_MACRO)\n#if !gsl_CHECK_CFG_TOGGLE_VALUE_(gsl_FEATURE_OWNER_MACRO)\n#pragma message(\"invalid configuration value gsl_FEATURE_OWNER_MACRO=\" gsl_STRINGIFY( \\\n        gsl_FEATURE_OWNER_MACRO) \", must be 0 or 1\")\n#endif\n#else\n#define gsl_FEATURE_OWNER_MACRO (gsl_CONFIG_DEFAULTS_VERSION == 0)  // default\n#endif\n#define gsl_FEATURE_OWNER_MACRO_() gsl_FEATURE_OWNER_MACRO\n\n#if defined(gsl_FEATURE_EXPERIMENTAL_RETURN_GUARD)\n#if !gsl_CHECK_CFG_TOGGLE_VALUE_(gsl_FEATURE_EXPERIMENTAL_RETURN_GUARD)\n#pragma message(                                                                            \\\n        \"invalid configuration value gsl_FEATURE_EXPERIMENTAL_RETURN_GUARD=\" gsl_STRINGIFY( \\\n                gsl_FEATURE_EXPERIMENTAL_RETURN_GUARD) \", must be 0 or 1\")\n#endif\n#else\n#define gsl_FEATURE_EXPERIMENTAL_RETURN_GUARD 0  // default\n#endif\n#define gsl_FEATURE_EXPERIMENTAL_RETURN_GUARD_() gsl_FEATURE_EXPERIMENTAL_RETURN_GUARD\n\n#if defined(gsl_FEATURE_GSL_LITE_NAMESPACE)\n#if !gsl_CHECK_CFG_TOGGLE_VALUE_(gsl_FEATURE_GSL_LITE_NAMESPACE)\n#pragma message(\"invalid configuration value gsl_FEATURE_GSL_LITE_NAMESPACE=\" gsl_STRINGIFY( \\\n        gsl_FEATURE_GSL_LITE_NAMESPACE) \", must be 0 or 1\")\n#endif\n#else\n#define gsl_FEATURE_GSL_LITE_NAMESPACE (gsl_CONFIG_DEFAULTS_VERSION >= 1)  // default\n#endif\n#define gsl_FEATURE_GSL_LITE_NAMESPACE_() gsl_FEATURE_GSL_LITE_NAMESPACE\n\n// Configuration: Other\n\n#if defined(gsl_CONFIG_TRANSPARENT_NOT_NULL)\n#if !gsl_CHECK_CFG_TOGGLE_VALUE_(gsl_CONFIG_TRANSPARENT_NOT_NULL)\n#pragma message(\"invalid configuration value gsl_CONFIG_TRANSPARENT_NOT_NULL=\" gsl_STRINGIFY( \\\n        gsl_CONFIG_TRANSPARENT_NOT_NULL) \", must be 0 or 1\")\n#endif\n#if gsl_CONFIG_TRANSPARENT_NOT_NULL && defined(gsl_CONFIG_NOT_NULL_GET_BY_CONST_REF)\n#error configuration option gsl_CONFIG_NOT_NULL_GET_BY_CONST_REF is meaningless if gsl_CONFIG_TRANSPARENT_NOT_NULL=1\n#endif\n#else\n#define gsl_CONFIG_TRANSPARENT_NOT_NULL (gsl_CONFIG_DEFAULTS_VERSION >= 1)  // default\n#endif\n#define gsl_CONFIG_TRANSPARENT_NOT_NULL_() gsl_CONFIG_TRANSPARENT_NOT_NULL\n\n#if !defined(gsl_CONFIG_DEPRECATE_TO_LEVEL)\n#if gsl_CONFIG_DEFAULTS_VERSION >= 1\n#define gsl_CONFIG_DEPRECATE_TO_LEVEL 6\n#else\n#define gsl_CONFIG_DEPRECATE_TO_LEVEL 0\n#endif\n#endif\n\n#if !defined(gsl_CONFIG_SPAN_INDEX_TYPE)\n#define gsl_CONFIG_SPAN_INDEX_TYPE std::size_t\n#endif\n#define gsl_CONFIG_SPAN_INDEX_TYPE_() gsl_CONFIG_SPAN_INDEX_TYPE\n\n#if !defined(gsl_CONFIG_INDEX_TYPE)\n#if gsl_CONFIG_DEFAULTS_VERSION >= 1\n// p0122r3 uses std::ptrdiff_t\n#define gsl_CONFIG_INDEX_TYPE std::ptrdiff_t\n#else\n#define gsl_CONFIG_INDEX_TYPE gsl_CONFIG_SPAN_INDEX_TYPE\n#endif\n#endif\n#define gsl_CONFIG_INDEX_TYPE_() gsl_CONFIG_INDEX_TYPE\n\n#if defined(gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR)\n#if !gsl_CHECK_CFG_TOGGLE_VALUE_(gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR)\n#pragma message(\"invalid configuration value gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR=\" gsl_STRINGIFY( \\\n        gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR) \", must be 0 or 1\")\n#endif\n#else\n#define gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR (gsl_CONFIG_DEFAULTS_VERSION >= 1)  // default\n#endif\n#define gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR_() gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR\n\n#if defined(gsl_CONFIG_NOT_NULL_GET_BY_CONST_REF)\n#if !gsl_CHECK_CFG_TOGGLE_VALUE_(gsl_CONFIG_NOT_NULL_GET_BY_CONST_REF)\n#pragma message(\"invalid configuration value gsl_CONFIG_NOT_NULL_GET_BY_CONST_REF=\" gsl_STRINGIFY( \\\n        gsl_CONFIG_NOT_NULL_GET_BY_CONST_REF) \", must be 0 or 1\")\n#endif\n#else\n#define gsl_CONFIG_NOT_NULL_GET_BY_CONST_REF 0  // default\n#endif\n#define gsl_CONFIG_NOT_NULL_GET_BY_CONST_REF_() gsl_CONFIG_NOT_NULL_GET_BY_CONST_REF\n\n#if defined(gsl_CONFIG_CONFIRMS_COMPILATION_ERRORS)\n#if !gsl_CHECK_CFG_TOGGLE_VALUE_(gsl_CONFIG_CONFIRMS_COMPILATION_ERRORS)\n#pragma message(                                                                             \\\n        \"invalid configuration value gsl_CONFIG_CONFIRMS_COMPILATION_ERRORS=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_CONFIRMS_COMPILATION_ERRORS) \", must be 0 or 1\")\n#endif\n#else\n#define gsl_CONFIG_CONFIRMS_COMPILATION_ERRORS 0  // default\n#endif\n#define gsl_CONFIG_CONFIRMS_COMPILATION_ERRORS_() gsl_CONFIG_CONFIRMS_COMPILATION_ERRORS\n\n#if defined(gsl_CONFIG_ALLOWS_SPAN_COMPARISON)\n#if !gsl_CHECK_CFG_TOGGLE_VALUE_(gsl_CONFIG_ALLOWS_SPAN_COMPARISON)\n#pragma message(\"invalid configuration value gsl_CONFIG_ALLOWS_SPAN_COMPARISON=\" gsl_STRINGIFY( \\\n        gsl_CONFIG_ALLOWS_SPAN_COMPARISON) \", must be 0 or 1\")\n#endif\n#else\n#define gsl_CONFIG_ALLOWS_SPAN_COMPARISON (gsl_CONFIG_DEFAULTS_VERSION == 0)  // default\n#endif\n#define gsl_CONFIG_ALLOWS_SPAN_COMPARISON_() gsl_CONFIG_ALLOWS_SPAN_COMPARISON\n\n#if defined(gsl_CONFIG_ALLOWS_NONSTRICT_SPAN_COMPARISON)\n#if !gsl_CHECK_CFG_TOGGLE_VALUE_(gsl_CONFIG_ALLOWS_NONSTRICT_SPAN_COMPARISON)\n#pragma message(                                                                                  \\\n        \"invalid configuration value gsl_CONFIG_ALLOWS_NONSTRICT_SPAN_COMPARISON=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_ALLOWS_NONSTRICT_SPAN_COMPARISON) \", must be 0 or 1\")\n#endif\n#else\n#define gsl_CONFIG_ALLOWS_NONSTRICT_SPAN_COMPARISON 1  // default\n#endif\n#define gsl_CONFIG_ALLOWS_NONSTRICT_SPAN_COMPARISON_() gsl_CONFIG_ALLOWS_NONSTRICT_SPAN_COMPARISON\n\n#if defined(gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR)\n#if !gsl_CHECK_CFG_TOGGLE_VALUE_(gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR)\n#pragma message(                                                                                          \\\n        \"invalid configuration value gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR) \", must be 0 or 1\")\n#endif\n#else\n#define gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR 0  // default\n#endif\n#define gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR_() \\\n    gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR\n\n#if defined(gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION)\n#if !gsl_CHECK_CFG_TOGGLE_VALUE_(gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION)\n#pragma message(                                                                             \\\n        \"invalid configuration value gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION) \", must be 0 or 1\")\n#endif\n#else\n#define gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION (gsl_CONFIG_DEFAULTS_VERSION >= 1)  // default\n#endif\n#define gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION_() gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION\n\n#if defined(gsl_CONFIG_CONTRACT_CHECKING_EXPECTS_OFF)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_CHECKING_EXPECTS_OFF)\n#pragma message(                                                                               \\\n        \"invalid configuration value gsl_CONFIG_CONTRACT_CHECKING_EXPECTS_OFF=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_CONTRACT_CHECKING_EXPECTS_OFF) \"; macro must be defined without value\")\n#endif\n#endif\n#if defined(gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF)\n#pragma message(                                                                               \\\n        \"invalid configuration value gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF) \"; macro must be defined without value\")\n#endif\n#endif\n#if defined(gsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF)\n#pragma message(                                                                              \\\n        \"invalid configuration value gsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF) \"; macro must be defined without value\")\n#endif\n#endif\n#if defined(gsl_CONFIG_CONTRACT_CHECKING_AUDIT)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_CHECKING_AUDIT)\n#pragma message(\"invalid configuration value gsl_CONFIG_CONTRACT_CHECKING_AUDIT=\" gsl_STRINGIFY( \\\n        gsl_CONFIG_CONTRACT_CHECKING_AUDIT) \"; macro must be defined without value\")\n#endif\n#endif\n#if defined(gsl_CONFIG_CONTRACT_CHECKING_ON)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_CHECKING_ON)\n#pragma message(\"invalid configuration value gsl_CONFIG_CONTRACT_CHECKING_ON=\" gsl_STRINGIFY( \\\n        gsl_CONFIG_CONTRACT_CHECKING_ON) \"; macro must be defined without value\")\n#endif\n#endif\n#if defined(gsl_CONFIG_CONTRACT_CHECKING_OFF)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_CHECKING_OFF)\n#pragma message(\"invalid configuration value gsl_CONFIG_CONTRACT_CHECKING_OFF=\" gsl_STRINGIFY( \\\n        gsl_CONFIG_CONTRACT_CHECKING_OFF) \"; macro must be defined without value\")\n#endif\n#endif\n#if defined(gsl_CONFIG_CONTRACT_VIOLATION_THROWS)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_VIOLATION_THROWS)\n#pragma message(\"invalid configuration value gsl_CONFIG_CONTRACT_VIOLATION_THROWS=\" gsl_STRINGIFY( \\\n        gsl_CONFIG_CONTRACT_VIOLATION_THROWS) \"; macro must be defined without value\")\n#endif\n#endif\n#if defined(gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES)\n#pragma message(                                                                               \\\n        \"invalid configuration value gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES) \"; macro must be defined without value\")\n#endif\n#endif\n#if defined(gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS)\n#pragma message(                                                                            \\\n        \"invalid configuration value gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS) \"; macro must be defined without value\")\n#endif\n#endif\n#if defined(gsl_CONFIG_CONTRACT_VIOLATION_TRAPS)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_VIOLATION_TRAPS)\n#pragma message(\"invalid configuration value gsl_CONFIG_CONTRACT_VIOLATION_TRAPS=\" gsl_STRINGIFY( \\\n        gsl_CONFIG_CONTRACT_VIOLATION_TRAPS) \"; macro must be defined without value\")\n#endif\n#endif\n#if defined(gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER)\n#pragma message(                                                                                  \\\n        \"invalid configuration value gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER) \"; macro must be defined without value\")\n#endif\n#endif\n#if defined(gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME)\n#pragma message(                                                                             \\\n        \"invalid configuration value gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME) \"; macro must be defined without value\")\n#endif\n#endif\n#if defined(gsl_CONFIG_UNENFORCED_CONTRACTS_ELIDE)\n#if !gsl_CHECK_CFG_NO_VALUE_(gsl_CONFIG_UNENFORCED_CONTRACTS_ELIDE)\n#pragma message(                                                                            \\\n        \"invalid configuration value gsl_CONFIG_UNENFORCED_CONTRACTS_ELIDE=\" gsl_STRINGIFY( \\\n                gsl_CONFIG_UNENFORCED_CONTRACTS_ELIDE) \"; macro must be defined without value\")\n#endif\n#endif\n\n#if 1 < defined(gsl_CONFIG_CONTRACT_CHECKING_AUDIT) + defined(gsl_CONFIG_CONTRACT_CHECKING_ON) + \\\n                defined(gsl_CONFIG_CONTRACT_CHECKING_OFF)\n#error only one of gsl_CONFIG_CONTRACT_CHECKING_AUDIT, gsl_CONFIG_CONTRACT_CHECKING_ON, and gsl_CONFIG_CONTRACT_CHECKING_OFF may be defined\n#endif\n#if 1 < defined(gsl_CONFIG_CONTRACT_VIOLATION_THROWS) +             \\\n                defined(gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES) + \\\n                defined(gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS) +    \\\n                defined(gsl_CONFIG_CONTRACT_VIOLATION_TRAPS) +      \\\n                defined(gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER)\n#error only one of gsl_CONFIG_CONTRACT_VIOLATION_THROWS, gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES, gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS, gsl_CONFIG_CONTRACT_VIOLATION_TRAPS, and gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER may be defined\n#endif\n#if 1 < defined(gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME) + \\\n                defined(gsl_CONFIG_UNENFORCED_CONTRACTS_ELIDE)\n#error only one of gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME and gsl_CONFIG_UNENFORCED_CONTRACTS_ELIDE may be defined\n#endif\n\n#if 0 == defined(gsl_CONFIG_CONTRACT_CHECKING_AUDIT) + defined(gsl_CONFIG_CONTRACT_CHECKING_ON) + \\\n                 defined(gsl_CONFIG_CONTRACT_CHECKING_OFF)\n// select default\n#define gsl_CONFIG_CONTRACT_CHECKING_ON\n#endif\n#if 0 == defined(gsl_CONFIG_CONTRACT_VIOLATION_THROWS) +             \\\n                 defined(gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES) + \\\n                 defined(gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS) +    \\\n                 defined(gsl_CONFIG_CONTRACT_VIOLATION_TRAPS) +      \\\n                 defined(gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER)\n// select default\n#define gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES\n#endif\n#if 0 == defined(gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME) + \\\n                 defined(gsl_CONFIG_UNENFORCED_CONTRACTS_ELIDE)\n// select default\n#define gsl_CONFIG_UNENFORCED_CONTRACTS_ELIDE\n#endif\n\n// C++ language version detection (C++20 is speculative):\n// Note: VC14.0/1900 (VS2015) lacks too much from C++14.\n\n#ifndef gsl_CPLUSPLUS\n#if defined(_MSVC_LANG) && !defined(__clang__)\n#define gsl_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG)\n#else\n#define gsl_CPLUSPLUS __cplusplus\n#endif\n#endif\n\n// C++ standard library version:\n\n#ifndef gsl_CPLUSPLUS_STDLIB\n#define gsl_CPLUSPLUS_STDLIB gsl_CPLUSPLUS\n#endif\n\n#define gsl_CPP98_OR_GREATER (gsl_CPLUSPLUS >= 199711L)\n#define gsl_CPP11_OR_GREATER (gsl_CPLUSPLUS >= 201103L)\n#define gsl_CPP14_OR_GREATER (gsl_CPLUSPLUS >= 201402L)\n#define gsl_CPP17_OR_GREATER (gsl_CPLUSPLUS >= 201703L)\n#define gsl_CPP20_OR_GREATER (gsl_CPLUSPLUS >= 202000L)\n\n// C++ language version (represent 98 as 3):\n\n#define gsl_CPLUSPLUS_V (gsl_CPLUSPLUS / 100 - (gsl_CPLUSPLUS > 200000 ? 2000 : 1994))\n\n// half-open range [lo..hi):\n#define gsl_BETWEEN(v, lo, hi) ((lo) <= (v) && (v) < (hi))\n\n// Compiler versions:\n\n// MSVC++  6.0  _MSC_VER == 1200  gsl_COMPILER_MSVC_VERSION ==  60  (Visual\n// Studio 6.0) MSVC++  7.0  _MSC_VER == 1300  gsl_COMPILER_MSVC_VERSION ==  70\n// (Visual Studio .NET 2002) MSVC++  7.1  _MSC_VER == 1310\n// gsl_COMPILER_MSVC_VERSION ==  71  (Visual Studio .NET 2003) MSVC++  8.0\n// _MSC_VER == 1400  gsl_COMPILER_MSVC_VERSION ==  80  (Visual Studio 2005)\n// MSVC++  9.0  _MSC_VER == 1500  gsl_COMPILER_MSVC_VERSION ==  90  (Visual\n// Studio 2008) MSVC++ 10.0  _MSC_VER == 1600  gsl_COMPILER_MSVC_VERSION == 100\n// (Visual Studio 2010) MSVC++ 11.0  _MSC_VER == 1700  gsl_COMPILER_MSVC_VERSION\n// == 110  (Visual Studio 2012) MSVC++ 12.0  _MSC_VER == 1800\n// gsl_COMPILER_MSVC_VERSION == 120  (Visual Studio 2013) MSVC++ 14.0  _MSC_VER\n// == 1900  gsl_COMPILER_MSVC_VERSION == 140  (Visual Studio 2015) MSVC++ 14.1\n// _MSC_VER >= 1910  gsl_COMPILER_MSVC_VERSION == 141  (Visual Studio 2017)\n// MSVC++ 14.2  _MSC_VER >= 1920  gsl_COMPILER_MSVC_VERSION == 142  (Visual\n// Studio 2019)\n\n#if defined(_MSC_VER) && !defined(__clang__)\n#define gsl_COMPILER_MSVC_VER (_MSC_VER)\n#define gsl_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * (5 + (_MSC_VER < 1900)))\n#define gsl_COMPILER_MSVC_VERSION_FULL (_MSC_VER - 100 * (5 + (_MSC_VER < 1900)))\n#else\n#define gsl_COMPILER_MSVC_VER 0\n#define gsl_COMPILER_MSVC_VERSION 0\n#define gsl_COMPILER_MSVC_VERSION_FULL 0\n#endif\n\n#define gsl_COMPILER_VERSION(major, minor, patch) (10 * (10 * (major) + (minor)) + (patch))\n\n// AppleClang  7.0.0  __apple_build_version__ ==  7000172\n// gsl_COMPILER_APPLECLANG_VERSION ==  700  (Xcode 7.0, 7.0.1) (LLVM  3.7.0)\n// AppleClang  7.0.0  __apple_build_version__ ==  7000176\n// gsl_COMPILER_APPLECLANG_VERSION ==  700  (Xcode 7.1) (LLVM  3.7.0) AppleClang\n// 7.0.2  __apple_build_version__ ==  7000181  gsl_COMPILER_APPLECLANG_VERSION\n// ==  702  (Xcode 7.2, 7.2.1)               (LLVM  3.7.0) AppleClang  7.3.0\n// __apple_build_version__ ==  7030029  gsl_COMPILER_APPLECLANG_VERSION ==  730\n// (Xcode 7.3)                      (LLVM  3.8.0) AppleClang  7.3.0\n// __apple_build_version__ ==  7030031  gsl_COMPILER_APPLECLANG_VERSION ==  730\n// (Xcode 7.3.1)                    (LLVM  3.8.0) AppleClang  8.0.0\n// __apple_build_version__ ==  8000038  gsl_COMPILER_APPLECLANG_VERSION ==  800\n// (Xcode 8.0)                      (LLVM  3.9.0) AppleClang  8.0.0\n// __apple_build_version__ ==  8000042  gsl_COMPILER_APPLECLANG_VERSION ==  800\n// (Xcode 8.1, 8.2, 8.2.1)          (LLVM  3.9.0) AppleClang  8.1.0\n// __apple_build_version__ ==  8020038  gsl_COMPILER_APPLECLANG_VERSION ==  810\n// (Xcode 8.3)                      (LLVM  3.9.0) AppleClang  8.1.0\n// __apple_build_version__ ==  8020041  gsl_COMPILER_APPLECLANG_VERSION ==  810\n// (Xcode 8.3.1)                    (LLVM  3.9.0) AppleClang  8.1.0\n// __apple_build_version__ ==  8020042  gsl_COMPILER_APPLECLANG_VERSION ==  810\n// (Xcode 8.3.2, 8.3.3)             (LLVM  3.9.0) AppleClang  9.0.0\n// __apple_build_version__ ==  9000037  gsl_COMPILER_APPLECLANG_VERSION ==  900\n// (Xcode 9.0)                      (LLVM  4.0.0) AppleClang  9.0.0\n// __apple_build_version__ ==  9000038  gsl_COMPILER_APPLECLANG_VERSION ==  900\n// (Xcode 9.1)                      (LLVM  4.0.0) AppleClang  9.0.0\n// __apple_build_version__ ==  9000039  gsl_COMPILER_APPLECLANG_VERSION ==  900\n// (Xcode 9.2)                      (LLVM  4.0.0) AppleClang  9.1.0\n// __apple_build_version__ ==  9020039  gsl_COMPILER_APPLECLANG_VERSION ==  910\n// (Xcode 9.3, 9.3.1)               (LLVM  5.0.2) AppleClang  9.1.0\n// __apple_build_version__ ==  9020039  gsl_COMPILER_APPLECLANG_VERSION ==  910\n// (Xcode 9.4, 9.4.1)               (LLVM  5.0.2) AppleClang 10.0.0\n// __apple_build_version__ == 10001145  gsl_COMPILER_APPLECLANG_VERSION == 1000\n// (Xcode 10.0, 10.1)               (LLVM  6.0.1) AppleClang 10.0.1\n// __apple_build_version__ == 10010046  gsl_COMPILER_APPLECLANG_VERSION == 1001\n// (Xcode 10.2, 10.2.1, 10.3)       (LLVM  7.0.0) AppleClang 11.0.0\n// __apple_build_version__ == 11000033  gsl_COMPILER_APPLECLANG_VERSION == 1100\n// (Xcode 11.1, 11.2, 11.3, 11.3.1) (LLVM  8.0.0) AppleClang 11.0.3\n// __apple_build_version__ == 11030032  gsl_COMPILER_APPLECLANG_VERSION == 1103\n// (Xcode 11.4, 11.4.1, 11.5, 11.6) (LLVM  9.0.0) AppleClang 12.0.0\n// __apple_build_version__ == 12000032  gsl_COMPILER_APPLECLANG_VERSION == 1200\n// (Xcode 12.0–12.4)                (LLVM 10.0.0) AppleClang 12.0.5\n// __apple_build_version__ == 12050022  gsl_COMPILER_APPLECLANG_VERSION == 1205\n// (Xcode 12.5)                     (LLVM 10.0.0)\n\n#if defined(__apple_build_version__)\n#define gsl_COMPILER_APPLECLANG_VERSION \\\n    gsl_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__)\n#define gsl_COMPILER_CLANG_VERSION 0\n#elif defined(__clang__)\n#define gsl_COMPILER_APPLECLANG_VERSION 0\n#define gsl_COMPILER_CLANG_VERSION \\\n    gsl_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__)\n#else\n#define gsl_COMPILER_APPLECLANG_VERSION 0\n#define gsl_COMPILER_CLANG_VERSION 0\n#endif\n\n#if defined(__GNUC__) && !defined(__clang__)\n#define gsl_COMPILER_GNUC_VERSION \\\n    gsl_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)\n#else\n#define gsl_COMPILER_GNUC_VERSION 0\n#endif\n\n#if defined(__NVCC__)\n#define gsl_COMPILER_NVCC_VERSION (__CUDACC_VER_MAJOR__ * 10 + __CUDACC_VER_MINOR__)\n#else\n#define gsl_COMPILER_NVCC_VERSION 0\n#endif\n\n#if defined(__ARMCC_VERSION)\n#define gsl_COMPILER_ARMCC_VERSION (__ARMCC_VERSION / 10000)\n#define gsl_COMPILER_ARMCC_VERSION_FULL __ARMCC_VERSION\n#else\n#define gsl_COMPILER_ARMCC_VERSION 0\n#define gsl_COMPILER_ARMCC_VERSION_FULL 0\n#endif\n\n// Compiler non-strict aliasing:\n\n#if defined(__clang__) || defined(__GNUC__)\n#define gsl_may_alias __attribute__((__may_alias__))\n#else\n#define gsl_may_alias\n#endif\n\n// Presence of gsl, language and library features:\n\n#define gsl_IN_STD(v) (((v) == 98 ? 3 : (v)) >= gsl_CPLUSPLUS_V)\n\n#define gsl_DEPRECATE_TO_LEVEL(level) (level <= gsl_CONFIG_DEPRECATE_TO_LEVEL)\n#define gsl_FEATURE_TO_STD(feature) gsl_IN_STD(gsl_FEATURE(feature##_TO_STD))\n#define gsl_FEATURE(feature) gsl_EVALF_(gsl_FEATURE_##feature##_)\n#define gsl_CONFIG(feature) gsl_EVALF_(gsl_CONFIG_##feature##_)\n#define gsl_HAVE(feature) gsl_EVALF_(gsl_HAVE_##feature##_)\n\n// Presence of wide character support:\n\n#ifdef __DJGPP__\n#define gsl_HAVE_WCHAR 0\n#else\n#define gsl_HAVE_WCHAR 1\n#endif\n#define gsl_HAVE_WCHAR_() gsl_HAVE_WCHAR\n\n// Presence of language & library features:\n\n#if gsl_COMPILER_CLANG_VERSION || gsl_COMPILER_APPLECLANG_VERSION\n#ifdef __OBJC__\n// There are a bunch of inconsistencies about __EXCEPTIONS and\n// __has_feature(cxx_exceptions) in Clang 3.4/3.5/3.6. We're interested in C++\n// exceptions, which can be checked by __has_feature(cxx_exceptions) in 3.5+. In\n// pre-3.5, __has_feature(cxx_exceptions) can be true if ObjC exceptions are\n// enabled, but C++ exceptions are disabled. The recommended way to check is\n// `__EXCEPTIONS && __has_feature(cxx_exceptions)`. See\n// https://releases.llvm.org/3.6.0/tools/clang/docs/ReleaseNotes.html#the-exceptions-macro\n// Note: this is only relevant in Objective-C++, thus the ifdef.\n#if __EXCEPTIONS && __has_feature(cxx_exceptions)\n#define gsl_HAVE_EXCEPTIONS 1\n#else\n#define gsl_HAVE_EXCEPTIONS 0\n#endif  // __EXCEPTIONS && __has_feature(cxx_exceptions)\n#else\n// clang-cl doesn't define __EXCEPTIONS for MSVC compatibility (see\n// https://reviews.llvm.org/D4065). Neither does Clang in MS-compatiblity mode.\n// Let's hope no one tries to build Objective-C++ code using MS-compatibility\n// mode or clang-cl.\n#if __has_feature(cxx_exceptions)\n#define gsl_HAVE_EXCEPTIONS 1\n#else\n#define gsl_HAVE_EXCEPTIONS 0\n#endif\n#endif\n#elif gsl_COMPILER_GNUC_VERSION\n#if gsl_BETWEEN(gsl_COMPILER_GNUC_VERSION, 1, 500)\n#ifdef __EXCEPTIONS\n#define gsl_HAVE_EXCEPTIONS 1\n#else\n#define gsl_HAVE_EXCEPTIONS 0\n#endif  // __EXCEPTIONS\n#else\n#ifdef __cpp_exceptions\n#define gsl_HAVE_EXCEPTIONS 1\n#else\n#define gsl_HAVE_EXCEPTIONS 0\n#endif  // __cpp_exceptions\n#endif  // gsl_BETWEEN(gsl_COMPILER_GNUC_VERSION, 1, 500)\n#elif gsl_COMPILER_MSVC_VERSION\n#ifdef _CPPUNWIND\n#define gsl_HAVE_EXCEPTIONS 1\n#else\n#define gsl_HAVE_EXCEPTIONS 0\n#endif  // _CPPUNWIND\n#else\n// For all other compilers, assume exceptions are always enabled.\n#define gsl_HAVE_EXCEPTIONS 1\n#endif\n#define gsl_HAVE_EXCEPTIONS_() gsl_HAVE_EXCEPTIONS\n\n#if defined(gsl_CONFIG_CONTRACT_VIOLATION_THROWS) && !gsl_HAVE_EXCEPTIONS\n#error Cannot use gsl_CONFIG_CONTRACT_VIOLATION_THROWS if exceptions are disabled.\n#endif  // defined( gsl_CONFIG_CONTRACT_VIOLATION_THROWS ) && !gsl_HAVE( \\\n        // EXCEPTIONS )\n\n#ifdef _HAS_CPP0X\n#define gsl_HAS_CPP0X _HAS_CPP0X\n#else\n#define gsl_HAS_CPP0X 0\n#endif\n\n#define gsl_CPP11_100 (gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1600)\n#define gsl_CPP11_110 (gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1700)\n#define gsl_CPP11_120 (gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1800)\n#define gsl_CPP11_140 (gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1900)\n\n#define gsl_CPP14_000 (gsl_CPP14_OR_GREATER)\n#define gsl_CPP14_120 (gsl_CPP14_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1800)\n#define gsl_CPP14_140 (gsl_CPP14_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1900)\n\n#define gsl_CPP17_000 (gsl_CPP17_OR_GREATER)\n#define gsl_CPP17_140 (gsl_CPP17_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1900)\n\n#define gsl_CPP11_140_CPP0X_90 (gsl_CPP11_140 || (gsl_COMPILER_MSVC_VER >= 1500 && gsl_HAS_CPP0X))\n#define gsl_CPP11_140_CPP0X_100 (gsl_CPP11_140 || (gsl_COMPILER_MSVC_VER >= 1600 && gsl_HAS_CPP0X))\n\n// Presence of C++11 language features:\n\n#define gsl_HAVE_C99_PREPROCESSOR gsl_CPP11_140\n#define gsl_HAVE_AUTO gsl_CPP11_100\n#define gsl_HAVE_RVALUE_REFERENCE gsl_CPP11_100\n#define gsl_HAVE_FUNCTION_REF_QUALIFIER \\\n    (gsl_CPP11_140 && !gsl_BETWEEN(gsl_COMPILER_GNUC_VERSION, 1, 481))\n#define gsl_HAVE_ENUM_CLASS gsl_CPP11_110\n#define gsl_HAVE_ALIAS_TEMPLATE gsl_CPP11_120\n#define gsl_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG gsl_CPP11_120\n#define gsl_HAVE_EXPLICIT gsl_CPP11_120\n#define gsl_HAVE_VARIADIC_TEMPLATE gsl_CPP11_120\n#define gsl_HAVE_IS_DELETE gsl_CPP11_120\n#define gsl_HAVE_CONSTEXPR_11 gsl_CPP11_140\n#define gsl_HAVE_IS_DEFAULT gsl_CPP11_140\n#define gsl_HAVE_NOEXCEPT gsl_CPP11_140\n#define gsl_HAVE_NORETURN (gsl_CPP11_140 && !gsl_BETWEEN(gsl_COMPILER_GNUC_VERSION, 1, 480))\n#define gsl_HAVE_EXPRESSION_SFINAE gsl_CPP11_140\n#define gsl_HAVE_OVERRIDE_FINAL gsl_CPP11_110\n\n#define gsl_HAVE_C99_PREPROCESSOR_() gsl_HAVE_C99_PREPROCESSOR\n#define gsl_HAVE_AUTO_() gsl_HAVE_AUTO\n#define gsl_HAVE_RVALUE_REFERENCE_() gsl_HAVE_RVALUE_REFERENCE\n#define gsl_HAVE_FUNCTION_REF_QUALIFIER_() gsl_HAVE_FUNCTION_REF_QUALIFIER\n#define gsl_HAVE_ENUM_CLASS_() gsl_HAVE_ENUM_CLASS\n#define gsl_HAVE_ALIAS_TEMPLATE_() gsl_HAVE_ALIAS_TEMPLATE\n#define gsl_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG_() gsl_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG\n#define gsl_HAVE_EXPLICIT_() gsl_HAVE_EXPLICIT\n#define gsl_HAVE_VARIADIC_TEMPLATE_() gsl_HAVE_VARIADIC_TEMPLATE\n#define gsl_HAVE_IS_DELETE_() gsl_HAVE_IS_DELETE\n#define gsl_HAVE_CONSTEXPR_11_() gsl_HAVE_CONSTEXPR_11\n#define gsl_HAVE_IS_DEFAULT_() gsl_HAVE_IS_DEFAULT\n#define gsl_HAVE_NOEXCEPT_() gsl_HAVE_NOEXCEPT\n#define gsl_HAVE_NORETURN_() gsl_HAVE_NORETURN\n#define gsl_HAVE_EXPRESSION_SFINAE_() gsl_HAVE_EXPRESSION_SFINAE\n#define gsl_HAVE_OVERRIDE_FINAL_() gsl_HAVE_OVERRIDE_FINAL\n\n// Presence of C++14 language features:\n\n#define gsl_HAVE_CONSTEXPR_14 (gsl_CPP14_000 && !gsl_BETWEEN(gsl_COMPILER_GNUC_VERSION, 1, 600))\n#define gsl_HAVE_DECLTYPE_AUTO gsl_CPP14_140\n#define gsl_HAVE_DEPRECATED (gsl_CPP14_140 && !gsl_BETWEEN(gsl_COMPILER_MSVC_VERSION, 1, 142))\n\n#define gsl_HAVE_CONSTEXPR_14_() gsl_HAVE_CONSTEXPR_14\n#define gsl_HAVE_DECLTYPE_AUTO_() gsl_HAVE_DECLTYPE_AUTO\n#define gsl_HAVE_DEPRECATED_() gsl_HAVE_DEPRECATED\n\n// Presence of C++17 language features:\n// MSVC: template parameter deduction guides since Visual Studio 2017 v15.7\n\n#define gsl_HAVE_ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE gsl_CPP17_000\n#define gsl_HAVE_DEDUCTION_GUIDES \\\n    (gsl_CPP17_000 && !gsl_BETWEEN(gsl_COMPILER_MSVC_VERSION_FULL, 1, 1414))\n#define gsl_HAVE_NODISCARD gsl_CPP17_000\n#define gsl_HAVE_CONSTEXPR_17 gsl_CPP17_OR_GREATER\n\n#define gsl_HAVE_ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE_() \\\n    gsl_HAVE_ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE\n#define gsl_HAVE_DEDUCTION_GUIDES_() gsl_HAVE_DEDUCTION_GUIDES\n#define gsl_HAVE_NODISCARD_() gsl_HAVE_NODISCARD\n#define gsl_HAVE_CONSTEXPR_17_() gsl_HAVE_CONSTEXPR_17\n\n// Presence of C++20 language features:\n\n#define gsl_HAVE_CONSTEXPR_20 gsl_CPP20_OR_GREATER\n\n#define gsl_HAVE_CONSTEXPR_20_() gsl_HAVE_CONSTEXPR_20\n\n// Presence of C++ library features:\n\n#if gsl_BETWEEN(gsl_COMPILER_ARMCC_VERSION, 1, 600)\n// Some versions of the ARM compiler apparently ship without a C++11 standard\n// library despite having some C++11 support.\n#define gsl_STDLIB_CPP98_OR_GREATER gsl_CPP98_OR_GREATER\n#define gsl_STDLIB_CPP11_OR_GREATER 0\n#define gsl_STDLIB_CPP14_OR_GREATER 0\n#define gsl_STDLIB_CPP17_OR_GREATER 0\n#define gsl_STDLIB_CPP20_OR_GREATER 0\n#else\n#define gsl_STDLIB_CPP98_OR_GREATER gsl_CPP98_OR_GREATER\n#define gsl_STDLIB_CPP11_OR_GREATER gsl_CPP11_OR_GREATER\n#define gsl_STDLIB_CPP14_OR_GREATER gsl_CPP14_OR_GREATER\n#define gsl_STDLIB_CPP17_OR_GREATER gsl_CPP17_OR_GREATER\n#define gsl_STDLIB_CPP20_OR_GREATER gsl_CPP20_OR_GREATER\n#endif\n\n#define gsl_STDLIB_CPP11_100 (gsl_STDLIB_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1600)\n#define gsl_STDLIB_CPP11_110 (gsl_STDLIB_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1700)\n#define gsl_STDLIB_CPP11_120 (gsl_STDLIB_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1800)\n#define gsl_STDLIB_CPP11_140 (gsl_STDLIB_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1900)\n\n#define gsl_STDLIB_CPP14_000 (gsl_STDLIB_CPP14_OR_GREATER)\n#define gsl_STDLIB_CPP14_120 (gsl_STDLIB_CPP14_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1800)\n#define gsl_STDLIB_CPP14_140 (gsl_STDLIB_CPP14_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1900)\n\n#define gsl_STDLIB_CPP17_000 (gsl_STDLIB_CPP17_OR_GREATER)\n#define gsl_STDLIB_CPP17_140 (gsl_STDLIB_CPP17_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1900)\n\n#define gsl_STDLIB_CPP11_140_CPP0X_90 \\\n    (gsl_STDLIB_CPP11_140 || (gsl_COMPILER_MSVC_VER >= 1500 && gsl_HAS_CPP0X))\n#define gsl_STDLIB_CPP11_140_CPP0X_100 \\\n    (gsl_STDLIB_CPP11_140 || (gsl_COMPILER_MSVC_VER >= 1600 && gsl_HAS_CPP0X))\n\n#define gsl_HAVE_ADDRESSOF gsl_STDLIB_CPP17_000\n#define gsl_HAVE_ARRAY gsl_STDLIB_CPP11_110\n#define gsl_HAVE_TYPE_TRAITS gsl_STDLIB_CPP11_110\n#define gsl_HAVE_TR1_TYPE_TRAITS gsl_STDLIB_CPP11_110\n#define gsl_HAVE_CONTAINER_DATA_METHOD gsl_STDLIB_CPP11_140_CPP0X_90\n#define gsl_HAVE_STD_DATA gsl_STDLIB_CPP17_000\n#ifdef __cpp_lib_ssize\n#define gsl_HAVE_STD_SSIZE 1\n#else\n#define gsl_HAVE_STD_SSIZE (gsl_COMPILER_GNUC_VERSION >= 1000 && __cplusplus > 201703L)\n#endif\n#define gsl_HAVE_HASH gsl_STDLIB_CPP11_120\n#define gsl_HAVE_SIZED_TYPES gsl_STDLIB_CPP11_140\n#define gsl_HAVE_MAKE_SHARED gsl_STDLIB_CPP11_140_CPP0X_100\n#define gsl_HAVE_SHARED_PTR gsl_STDLIB_CPP11_140_CPP0X_100\n#define gsl_HAVE_UNIQUE_PTR gsl_STDLIB_CPP11_140_CPP0X_100\n#define gsl_HAVE_MAKE_UNIQUE gsl_STDLIB_CPP14_120\n#define gsl_HAVE_MOVE_FORWARD gsl_STDLIB_CPP11_100\n#define gsl_HAVE_NULLPTR gsl_STDLIB_CPP11_100\n#define gsl_HAVE_UNCAUGHT_EXCEPTIONS gsl_STDLIB_CPP17_140\n#define gsl_HAVE_ADD_CONST gsl_HAVE_TYPE_TRAITS\n#define gsl_HAVE_INITIALIZER_LIST gsl_STDLIB_CPP11_120\n#define gsl_HAVE_INTEGRAL_CONSTANT gsl_HAVE_TYPE_TRAITS\n#define gsl_HAVE_REMOVE_CONST gsl_HAVE_TYPE_TRAITS\n#define gsl_HAVE_REMOVE_REFERENCE gsl_HAVE_TYPE_TRAITS\n#define gsl_HAVE_REMOVE_CVREF gsl_STDLIB_CPP20_OR_GREATER\n#define gsl_HAVE_TR1_ADD_CONST gsl_HAVE_TR1_TYPE_TRAITS\n#define gsl_HAVE_TR1_INTEGRAL_CONSTANT gsl_HAVE_TR1_TYPE_TRAITS\n#define gsl_HAVE_TR1_REMOVE_CONST gsl_HAVE_TR1_TYPE_TRAITS\n#define gsl_HAVE_TR1_REMOVE_REFERENCE gsl_HAVE_TR1_TYPE_TRAITS\n\n#define gsl_HAVE_ADDRESSOF_() gsl_HAVE_ADDRESSOF\n#define gsl_HAVE_ARRAY_() gsl_HAVE_ARRAY\n#define gsl_HAVE_TYPE_TRAITS_() gsl_HAVE_TYPE_TRAITS\n#define gsl_HAVE_TR1_TYPE_TRAITS_() gsl_HAVE_TR1_TYPE_TRAITS\n#define gsl_HAVE_CONTAINER_DATA_METHOD_() gsl_HAVE_CONTAINER_DATA_METHOD\n#define gsl_HAVE_HASH_() gsl_HAVE_HASH\n#define gsl_HAVE_STD_DATA_() gsl_HAVE_STD_DATA\n#define gsl_HAVE_STD_SSIZE_() gsl_HAVE_STD_SSIZE\n#define gsl_HAVE_SIZED_TYPES_() gsl_HAVE_SIZED_TYPES\n#define gsl_HAVE_MAKE_SHARED_() gsl_HAVE_MAKE_SHARED\n#define gsl_HAVE_MOVE_FORWARD_() gsl_HAVE_MOVE_FORWARD\n#define gsl_HAVE_NULLPTR_() \\\n    gsl_HAVE_NULLPTR  // It's a language feature but needs library support, so we \\\n                      // list it as a library feature.\n#define gsl_HAVE_SHARED_PTR_() gsl_HAVE_SHARED_PTR\n#define gsl_HAVE_UNIQUE_PTR_() gsl_HAVE_UNIQUE_PTR\n#define gsl_HAVE_MAKE_UNIQUE_() gsl_HAVE_MAKE_UNIQUE\n#define gsl_HAVE_UNCAUGHT_EXCEPTIONS_() gsl_HAVE_UNCAUGHT_EXCEPTIONS\n#define gsl_HAVE_ADD_CONST_() gsl_HAVE_ADD_CONST\n#define gsl_HAVE_INITIALIZER_LIST_() \\\n    gsl_HAVE_INITIALIZER_LIST  // It's a language feature but needs library \\\n                               // support, so we list it as a library feature.\n#define gsl_HAVE_INTEGRAL_CONSTANT_() gsl_HAVE_INTEGRAL_CONSTANT\n#define gsl_HAVE_REMOVE_CONST_() gsl_HAVE_REMOVE_CONST\n#define gsl_HAVE_REMOVE_REFERENCE_() gsl_HAVE_REMOVE_REFERENCE\n#define gsl_HAVE_REMOVE_CVREF_() gsl_HAVE_REMOVE_CVREF\n#define gsl_HAVE_TR1_ADD_CONST_() gsl_HAVE_TR1_ADD_CONST\n#define gsl_HAVE_TR1_INTEGRAL_CONSTANT_() gsl_HAVE_TR1_INTEGRAL_CONSTANT\n#define gsl_HAVE_TR1_REMOVE_CONST_() gsl_HAVE_TR1_REMOVE_CONST\n#define gsl_HAVE_TR1_REMOVE_REFERENCE_() gsl_HAVE_TR1_REMOVE_REFERENCE\n\n// C++ feature usage:\n\n#if gsl_HAVE(ADDRESSOF)\n#define gsl_ADDRESSOF(x) std::addressof(x)\n#else\n#define gsl_ADDRESSOF(x) (&x)\n#endif\n\n#if gsl_HAVE(CONSTEXPR_11)\n#define gsl_constexpr constexpr\n#else\n#define gsl_constexpr /*constexpr*/\n#endif\n\n#if gsl_HAVE(CONSTEXPR_14)\n#define gsl_constexpr14 constexpr\n#else\n#define gsl_constexpr14 /*constexpr*/\n#endif\n\n#if gsl_HAVE(CONSTEXPR_17)\n#define gsl_constexpr17 constexpr\n#else\n#define gsl_constexpr17 /*constexpr*/\n#endif\n\n#if gsl_HAVE(CONSTEXPR_20)\n#define gsl_constexpr20 constexpr\n#else\n#define gsl_constexpr20 /*constexpr*/\n#endif\n\n#if gsl_HAVE(EXPLICIT)\n#define gsl_explicit explicit\n#else\n#define gsl_explicit /*explicit*/\n#endif\n\n#if gsl_FEATURE(IMPLICIT_MACRO)\n#define implicit /*implicit*/\n#endif\n\n#if gsl_HAVE(IS_DELETE)\n#define gsl_is_delete = delete\n#else\n#define gsl_is_delete\n#endif\n\n#if gsl_HAVE(IS_DELETE)\n#define gsl_is_delete_access public\n#else\n#define gsl_is_delete_access private\n#endif\n\n#if gsl_HAVE(NOEXCEPT)\n#define gsl_noexcept noexcept\n#define gsl_noexcept_if(expr) noexcept(expr)\n#else\n#define gsl_noexcept throw()\n#define gsl_noexcept_if(expr) /*noexcept( expr )*/\n#endif\n#if defined(gsl_TESTING_)\n#define gsl_noexcept_not_testing\n#else\n#define gsl_noexcept_not_testing gsl_noexcept\n#endif\n\n#if gsl_HAVE(NULLPTR)\n#define gsl_nullptr nullptr\n#else\n#define gsl_nullptr NULL\n#endif\n\n#if gsl_HAVE(NODISCARD)\n#define gsl_NODISCARD [[nodiscard]]\n#else\n#define gsl_NODISCARD\n#endif\n\n#if gsl_HAVE(NORETURN)\n#define gsl_NORETURN [[noreturn]]\n#elif defined(_MSC_VER)\n#define gsl_NORETURN __declspec(noreturn)\n#elif gsl_COMPILER_GNUC_VERSION || gsl_COMPILER_CLANG_VERSION || \\\n        gsl_COMPILER_APPLECLANG_VERSION || gsl_COMPILER_ARMCC_VERSION\n#define gsl_NORETURN __attribute__((noreturn))\n#else\n#define gsl_NORETURN\n#endif\n\n#if gsl_HAVE(DEPRECATED) && !defined(gsl_TESTING_)\n#define gsl_DEPRECATED [[deprecated]]\n#define gsl_DEPRECATED_MSG(msg) [[deprecated(msg)]]\n#else\n#define gsl_DEPRECATED\n#define gsl_DEPRECATED_MSG(msg)\n#endif\n\n#if gsl_HAVE(C99_PREPROCESSOR)\n#if gsl_CPP20_OR_GREATER\n#define gsl_CONSTRAINT(...) __VA_ARGS__\n#else\n#define gsl_CONSTRAINT(...) typename\n#endif\n#endif\n\n#if gsl_HAVE(TYPE_TRAITS)\n#define gsl_STATIC_ASSERT_(cond, msg) static_assert(cond, msg)\n#else\n#define gsl_STATIC_ASSERT_(cond, msg) ((void)sizeof(char[1 - 2 * !!(cond)]))\n#endif\n\n#if gsl_HAVE(TYPE_TRAITS)\n\n#define gsl_DEFINE_ENUM_BITMASK_OPERATORS_(ENUM)                                                 \\\n    gsl_NODISCARD gsl_api inline gsl_constexpr ENUM operator~(ENUM val) gsl_noexcept {           \\\n        typedef typename ::gsl::std11::underlying_type<ENUM>::type U;                            \\\n        return ENUM(~U(val));                                                                    \\\n    }                                                                                            \\\n    gsl_NODISCARD gsl_api inline gsl_constexpr ENUM operator|(ENUM lhs, ENUM rhs) gsl_noexcept { \\\n        typedef typename ::gsl::std11::underlying_type<ENUM>::type U;                            \\\n        return ENUM(U(lhs) | U(rhs));                                                            \\\n    }                                                                                            \\\n    gsl_NODISCARD gsl_api inline gsl_constexpr ENUM operator&(ENUM lhs, ENUM rhs) gsl_noexcept { \\\n        typedef typename ::gsl::std11::underlying_type<ENUM>::type U;                            \\\n        return ENUM(U(lhs) & U(rhs));                                                            \\\n    }                                                                                            \\\n    gsl_NODISCARD gsl_api inline gsl_constexpr ENUM operator^(ENUM lhs, ENUM rhs) gsl_noexcept { \\\n        typedef typename ::gsl::std11::underlying_type<ENUM>::type U;                            \\\n        return ENUM(U(lhs) ^ U(rhs));                                                            \\\n    }                                                                                            \\\n    gsl_api inline gsl_constexpr14 ENUM &operator|=(ENUM &lhs, ENUM rhs) gsl_noexcept {          \\\n        return lhs = lhs | rhs;                                                                  \\\n    }                                                                                            \\\n    gsl_api inline gsl_constexpr14 ENUM &operator&=(ENUM &lhs, ENUM rhs) gsl_noexcept {          \\\n        return lhs = lhs & rhs;                                                                  \\\n    }                                                                                            \\\n    gsl_api inline gsl_constexpr14 ENUM &operator^=(ENUM &lhs, ENUM rhs) gsl_noexcept {          \\\n        return lhs = lhs ^ rhs;                                                                  \\\n    }\n\n#define gsl_DEFINE_ENUM_RELATIONAL_OPERATORS_(ENUM)                                               \\\n    gsl_NODISCARD gsl_api inline gsl_constexpr bool operator<(ENUM lhs, ENUM rhs) gsl_noexcept {  \\\n        typedef typename ::gsl::std11::underlying_type<ENUM>::type U;                             \\\n        return U(lhs) < U(rhs);                                                                   \\\n    }                                                                                             \\\n    gsl_NODISCARD gsl_api inline gsl_constexpr bool operator>(ENUM lhs, ENUM rhs) gsl_noexcept {  \\\n        typedef typename ::gsl::std11::underlying_type<ENUM>::type U;                             \\\n        return U(lhs) > U(rhs);                                                                   \\\n    }                                                                                             \\\n    gsl_NODISCARD gsl_api inline gsl_constexpr bool operator<=(ENUM lhs, ENUM rhs) gsl_noexcept { \\\n        typedef typename ::gsl::std11::underlying_type<ENUM>::type U;                             \\\n        return U(lhs) <= U(rhs);                                                                  \\\n    }                                                                                             \\\n    gsl_NODISCARD gsl_api inline gsl_constexpr bool operator>=(ENUM lhs, ENUM rhs) gsl_noexcept { \\\n        typedef typename ::gsl::std11::underlying_type<ENUM>::type U;                             \\\n        return U(lhs) >= U(rhs);                                                                  \\\n    }\n\n//\n// Defines bitmask operators `|`, `&`, `^`, `~`, `|=`, `&=`, and `^=` for the\n// given enum type.\n//\n//     enum class Vegetables { tomato = 0b001, onion = 0b010, eggplant = 0b100\n//     }; gsl_DEFINE_ENUM_BITMASK_OPERATORS( Vegetables )\n//\n#define gsl_DEFINE_ENUM_BITMASK_OPERATORS(ENUM) gsl_DEFINE_ENUM_BITMASK_OPERATORS_(ENUM)\n\n//\n// Defines relational operators `<`, `>`, `<=`, `>=` for the given enum type.\n//\n//     enum class OperatorPrecedence { additive = 0, multiplicative = 1, power =\n//     2 }; gsl_DEFINE_ENUM_RELATIONAL_OPERATORS( OperatorPrecedence )\n//\n#define gsl_DEFINE_ENUM_RELATIONAL_OPERATORS(ENUM) gsl_DEFINE_ENUM_RELATIONAL_OPERATORS_(ENUM)\n\n#endif  // gsl_HAVE( TYPE_TRAITS )\n\n#define gsl_DIMENSION_OF(a) (sizeof(a) / sizeof(0 [a]))\n\n// Method enabling (C++98, VC120 (VS2013) cannot use __VA_ARGS__)\n\n#if gsl_HAVE(EXPRESSION_SFINAE)\n#define gsl_TRAILING_RETURN_TYPE_(T) auto\n#define gsl_RETURN_DECLTYPE_(EXPR) ->decltype(EXPR)\n#else\n#define gsl_TRAILING_RETURN_TYPE_(T) T\n#define gsl_RETURN_DECLTYPE_(EXPR)\n#endif\n\n// NOTE: When using SFINAE in gsl-lite, please note that overloads of function\n// templates must always use SFINAE with non-type default arguments\n//       as explained in\n//       https://en.cppreference.com/w/cpp/types/enable_if#Notes.\n//       `gsl_ENABLE_IF_()` implements graceful fallback to default type\n//       arguments (for compilers that don't support non-type default\n//       arguments); please verify that this is appropriate in the given\n//       situation, and add additional checks if necessary.\n//\n//       Also, please note that `gsl_ENABLE_IF_()` doesn't enforce the\n//       constraint at all if no compiler/library support is available (i.e.\n//       pre-C++11).\n\n#if gsl_HAVE(TYPE_TRAITS) && gsl_HAVE(DEFAULT_FUNCTION_TEMPLATE_ARG)\n#if !gsl_BETWEEN(gsl_COMPILER_MSVC_VERSION, 1, \\\n                 140)  // VS 2013 seems to have trouble with SFINAE for default \\\n                       // non-type arguments\n#define gsl_ENABLE_IF_(VA) , typename std::enable_if<(VA), int>::type = 0\n#else\n#define gsl_ENABLE_IF_(VA) , typename = typename std::enable_if<(VA), ::gsl::detail::enabler>::type\n#endif\n#else\n#define gsl_ENABLE_IF_(VA)\n#endif\n\n// Other features:\n\n#define gsl_HAVE_CONSTRAINED_SPAN_CONTAINER_CTOR \\\n    (gsl_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG && gsl_HAVE_CONTAINER_DATA_METHOD)\n#define gsl_HAVE_CONSTRAINED_SPAN_CONTAINER_CTOR_() gsl_HAVE_CONSTRAINED_SPAN_CONTAINER_CTOR\n\n#define gsl_HAVE_UNCONSTRAINED_SPAN_CONTAINER_CTOR \\\n    (gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR && gsl_COMPILER_NVCC_VERSION == 0)\n#define gsl_HAVE_UNCONSTRAINED_SPAN_CONTAINER_CTOR_() gsl_HAVE_UNCONSTRAINED_SPAN_CONTAINER_CTOR\n\n// GSL API (e.g. for CUDA platform):\n\n// Guidelines for using `gsl_api`:\n//\n// NVCC imposes the restriction that a function annotated `__host__ __device__`\n// cannot call host-only or device-only functions. This makes `gsl_api`\n// inappropriate for generic functions that call unknown code, e.g. the template\n// constructors of `span<>` or functions like `finally()` which accept an\n// arbitrary  function object. It is often preferable to annotate functions only\n// with `gsl_constexpr` or `gsl_constexpr14`. The \"extended constexpr\" mode of\n// NVCC (currently an experimental feature) will implicitly consider constexpr\n// functions `__host__ __device__` functions but tolerates calls to host-only or\n// device-only functions.\n\n#ifndef gsl_api\n#ifdef __CUDACC__\n#define gsl_api __host__ __device__\n#else\n#define gsl_api /*gsl_api*/\n#endif\n#endif\n\n// Additional includes:\n\n#if !gsl_CPP11_OR_GREATER\n#include <algorithm>  // for swap() before C++11\n#endif                // ! gsl_CPP11_OR_GREATER\n\n#if gsl_HAVE(ARRAY)\n#include <array>  // indirectly includes reverse_iterator<>\n#endif\n\n#if !gsl_HAVE(ARRAY)\n#include <iterator>  // for reverse_iterator<>\n#endif\n\n#if !gsl_HAVE(CONSTRAINED_SPAN_CONTAINER_CTOR) || !gsl_HAVE(AUTO)\n#include <vector>\n#endif\n\n#if gsl_HAVE(INITIALIZER_LIST)\n#include <initializer_list>\n#endif\n\n#if defined(gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS)\n#include <cassert>\n#endif\n\n#if defined(gsl_CONFIG_CONTRACT_VIOLATION_TRAPS) && \\\n        gsl_COMPILER_MSVC_VERSION >= 110  // __fastfail() supported by VS 2012 and later\n#include <intrin.h>\n#endif\n\n#if gsl_HAVE(ENUM_CLASS) && gsl_COMPILER_ARMCC_VERSION\n#include <endian.h>\n#endif\n\n#if gsl_HAVE(TYPE_TRAITS)\n#include <type_traits>  // for enable_if<>,\n// add_const<>, add_pointer<>, common_type<>, make_signed<>, remove_cv<>,\n// remove_const<>, remove_volatile<>, remove_reference<>, remove_cvref<>,\n// remove_pointer<>, underlying_type<>, is_assignable<>, is_constructible<>,\n// is_const<>, is_convertible<>, is_integral<>, is_pointer<>, is_signed<>,\n// integral_constant<>, declval()\n#elif gsl_HAVE(TR1_TYPE_TRAITS)\n#include <tr1/type_traits>  // for add_const<>, remove_cv<>, remove_const<>, remove_volatile<>, remove_reference<>, integral_constant<>\n#endif\n\n#if gsl_FEATURE(EXPERIMENTAL_RETURN_GUARD)\n\n// Declare __cxa_get_globals() or equivalent in namespace gsl::detail for\n// uncaught_exceptions():\n\n#if !gsl_HAVE(UNCAUGHT_EXCEPTIONS)\n#if defined(_MSC_VER)  // MS-STL with either MSVC or clang-cl\nnamespace gsl {\nnamespace detail {\nextern \"C\" char *__cdecl _getptd();\n}\n}  // namespace gsl\n#elif gsl_COMPILER_CLANG_VERSION || gsl_COMPILER_GNUC_VERSION || gsl_COMPILER_APPLECLANG_VERSION\n#if defined(__GLIBCXX__) || defined(__GLIBCPP__)  // libstdc++: prototype from cxxabi.h\n#include <cxxabi.h>\n#elif !defined(BOOST_CORE_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED_)  // libc++: prototype from \\\n                                                              // Boost?\n#if defined(__FreeBSD__) || defined(__OpenBSD__)\nnamespace __cxxabiv1 {\nstruct __cxa_eh_globals;\nextern \"C\" __cxa_eh_globals *__cxa_get_globals();\n}  // namespace __cxxabiv1\n#else\nnamespace __cxxabiv1 {\nstruct __cxa_eh_globals;\nextern \"C\" __cxa_eh_globals *__cxa_get_globals() gsl_noexcept;\n}  // namespace __cxxabiv1\n#endif\n#endif\nnamespace gsl {\nnamespace detail {\nusing ::__cxxabiv1::__cxa_get_globals;\n}\n}  // namespace gsl\n#endif\n#endif  // ! gsl_HAVE( UNCAUGHT_EXCEPTIONS )\n#endif  // gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD )\n\n// MSVC warning suppression macros:\n\n#if gsl_COMPILER_MSVC_VERSION >= 140 && !gsl_COMPILER_NVCC_VERSION\n#define gsl_SUPPRESS_MSGSL_WARNING(expr) /* Pimm: note disabled for intel [[gsl::suppress(expr)]]*/\n#define gsl_SUPPRESS_MSVC_WARNING(code, descr) __pragma(warning(suppress : code))\n#define gsl_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable : codes))\n#define gsl_RESTORE_MSVC_WARNINGS() __pragma(warning(pop))\n#else\n// TODO: define for Clang\n#define gsl_SUPPRESS_MSGSL_WARNING(expr)\n#define gsl_SUPPRESS_MSVC_WARNING(code, descr)\n#define gsl_DISABLE_MSVC_WARNINGS(codes)\n#define gsl_RESTORE_MSVC_WARNINGS()\n#endif\n\n// Suppress the following MSVC GSL warnings:\n// - C26432: gsl::c.21 : if you define or delete any default operation in the\n// type '...', define or delete them all\n// - C26410: gsl::r.32 : the parameter 'ptr' is a reference to const unique\n// pointer, use const T* or const T& instead\n// - C26415: gsl::r.30 : smart pointer parameter 'ptr' is used only to access\n// contained pointer. Use T* or T& instead\n// - C26418: gsl::r.36 : shared pointer parameter 'ptr' is not copied or moved.\n// Use T* or T& instead\n// - C26472: gsl::t.1  : don't use a static_cast for arithmetic conversions;\n//                       use brace initialization, gsl::narrow_cast or\n//                       gsl::narrow\n// - C26439: gsl::f.6  : special function 'function' can be declared 'noexcept'\n// - C26440: gsl::f.6  : function 'function' can be declared 'noexcept'\n// - C26455: gsl::f.6  : default constructor may not throw. Declare it\n// 'noexcept'\n// - C26473: gsl::t.1  : don't cast between pointer types where the source type\n// and the target type are the same\n// - C26481: gsl::b.1  : don't use pointer arithmetic. Use span instead\n// - C26482: gsl::b.2  : only index into arrays using constant expressions\n// - C26446: gdl::b.4  : prefer to use gsl::at() instead of unchecked subscript\n// operator\n// - C26490: gsl::t.1  : don't use reinterpret_cast\n// - C26487: gsl::l.4  : don't return a pointer '(<some number>'s result)' that\n// may be invalid\n// - C26457: es.48     : (void) should not be used to ignore return values, use\n// 'std::ignore =' instead\n\ngsl_DISABLE_MSVC_WARNINGS(\n        26432 26410 26415 26418 26472 26439 26440 26455 26473 26481 26482 26446 26490 26487 26457)\n\n        namespace gsl {\n    // forward declare span<>:\n\n    template <class T>\n    class span;\n\n    // C++98 emulation:\n\n    namespace std98 {\n\n    // We implement `equal()` and `lexicographical_compare()` here to avoid having\n    // to pull in the <algorithm> header.\n    template <class InputIt1, class InputIt2>\n    bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2) {\n        // Implementation borrowed from\n        // https://en.cppreference.com/w/cpp/algorithm/equal.\n        for (; first1 != last1; ++first1, ++first2) {\n            if (!(*first1 == *first2))\n                return false;\n        }\n        return true;\n    }\n    template <class InputIt1, class InputIt2>\n    bool lexicographical_compare(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) {\n        // Implementation borrowed from\n        // https://en.cppreference.com/w/cpp/algorithm/lexicographical_compare.\n        for (; first1 != last1 && first2 != last2; ++first1, (void)++first2) {\n            if (*first1 < *first2)\n                return true;\n            if (*first2 < *first1)\n                return false;\n        }\n        return first1 == last1 && first2 != last2;\n    }\n\n    }  // namespace std98\n\n    // C++11 emulation:\n\n    namespace std11 {\n\n#if gsl_HAVE(ADD_CONST)\n\n    using std::add_const;\n\n#elif gsl_HAVE(TR1_ADD_CONST)\n\n    using std::tr1::add_const;\n\n#else\n\n    template <class T>\n    struct add_const {\n        typedef const T type;\n    };\n\n#endif  // gsl_HAVE( ADD_CONST )\n\n#if gsl_HAVE(REMOVE_CONST)\n\n    using std::remove_const;\n    using std::remove_cv;\n    using std::remove_volatile;\n\n#elif gsl_HAVE(TR1_REMOVE_CONST)\n\n    using std::tr1::remove_const;\n    using std::tr1::remove_cv;\n    using std::tr1::remove_volatile;\n\n#else\n\n    template <class T>\n    struct remove_const {\n        typedef T type;\n    };\n    template <class T>\n    struct remove_const<T const> {\n        typedef T type;\n    };\n\n    template <class T>\n    struct remove_volatile {\n        typedef T type;\n    };\n    template <class T>\n    struct remove_volatile<T volatile> {\n        typedef T type;\n    };\n\n    template <class T>\n    struct remove_cv {\n        typedef typename remove_volatile<typename remove_const<T>::type>::type type;\n    };\n\n#endif  // gsl_HAVE( REMOVE_CONST )\n\n#if gsl_HAVE(REMOVE_REFERENCE)\n\n    using std::remove_reference;\n\n#elif gsl_HAVE(TR1_REMOVE_REFERENCE)\n\n    using std::tr1::remove_reference;\n\n#else\n\n    template <class T>\n    struct remove_reference {\n        typedef T type;\n    };\n    template <class T>\n    struct remove_reference<T &> {\n        typedef T type;\n    };\n#if gsl_HAVE(RVALUE_REFERENCE)\n    template <class T>\n    struct remove_reference<T &&> {\n        typedef T type;\n    };\n#endif\n\n#endif  // gsl_HAVE( REMOVE_REFERENCE )\n\n#if gsl_HAVE(INTEGRAL_CONSTANT)\n\n    using std::false_type;\n    using std::integral_constant;\n    using std::true_type;\n\n#elif gsl_HAVE(TR1_INTEGRAL_CONSTANT)\n\n    using std::tr1::false_type;\n    using std::tr1::integral_constant;\n    using std::tr1::true_type;\n\n#else\n\n    template <class T, T v>\n    struct integral_constant {\n        enum { value = v };\n    };\n    typedef integral_constant<bool, true> true_type;\n    typedef integral_constant<bool, false> false_type;\n\n#endif\n\n#if gsl_HAVE(TYPE_TRAITS)\n\n    using std::underlying_type;\n\n#elif gsl_HAVE(TR1_TYPE_TRAITS)\n\n    using std::tr1::underlying_type;\n\n#else\n\n    // We could try to define `underlying_type<>` for pre-C++11 here, but let's\n    // not until someone actually needs it.\n\n#endif\n\n    }  // namespace std11\n\n    // C++14 emulation:\n\n    namespace std14 {\n\n#if gsl_HAVE(UNIQUE_PTR)\n#if gsl_HAVE(MAKE_UNIQUE)\n\n    using std::make_unique;\n\n#elif gsl_HAVE(VARIADIC_TEMPLATE)\n\n    template <class T, class... Args>\n    gsl_NODISCARD std::unique_ptr<T> make_unique(Args &&...args) {\n        return std::unique_ptr<T>(new T(std::forward<Args>(args)...));\n    }\n\n#endif  // gsl_HAVE( MAKE_UNIQUE ), gsl_HAVE( VARIADIC_TEMPLATE )\n#endif  // gsl_HAVE( UNIQUE_PTR )\n\n    }  // namespace std14\n\n    namespace detail {\n\n#if gsl_HAVE(VARIADIC_TEMPLATE)\n\n    template <bool V0, class T0, class... Ts>\n    struct conjunction_ {\n        using type = T0;\n    };\n    template <class T0, class T1, class... Ts>\n    struct conjunction_<true, T0, T1, Ts...> : conjunction_<T1::value, T1, Ts...> {};\n    template <bool V0, class T0, class... Ts>\n    struct disjunction_ {\n        using type = T0;\n    };\n    template <class T0, class T1, class... Ts>\n    struct disjunction_<false, T0, T1, Ts...> : disjunction_<T1::value, T1, Ts...> {};\n\n#endif\n\n    template <typename>\n    struct dependent_false : std11::integral_constant<bool, false> {};\n\n    }  // namespace detail\n\n    // C++17 emulation:\n\n    namespace std17 {\n\n    template <bool v>\n    struct bool_constant : std11::integral_constant<bool, v> {};\n\n#if gsl_CPP11_120\n\n    template <class... Ts>\n    struct conjunction;\n    template <>\n    struct conjunction<> : std11::true_type {};\n    template <class T0, class... Ts>\n    struct conjunction<T0, Ts...> : detail::conjunction_<T0::value, T0, Ts...>::type {};\n    template <class... Ts>\n    struct disjunction;\n    template <>\n    struct disjunction<> : std11::false_type {};\n    template <class T0, class... Ts>\n    struct disjunction<T0, Ts...> : detail::disjunction_<T0::value, T0, Ts...>::type {};\n    template <class T>\n    struct negation : std11::integral_constant<bool, !T::value> {};\n\n#if gsl_CPP14_OR_GREATER\n\n    template <class... Ts>\n    constexpr bool conjunction_v = conjunction<Ts...>::value;\n    template <class... Ts>\n    constexpr bool disjunction_v = disjunction<Ts...>::value;\n    template <class T>\n    constexpr bool negation_v = negation<T>::value;\n\n#endif  // gsl_CPP14_OR_GREATER\n\n    template <class... Ts>\n    struct make_void {\n        typedef void type;\n    };\n\n    template <class... Ts>\n    using void_t = typename make_void<Ts...>::type;\n\n#endif  // gsl_CPP11_120\n\n#if gsl_HAVE(CONSTRAINED_SPAN_CONTAINER_CTOR)\n\n    template <class T, size_t N>\n    gsl_NODISCARD gsl_api inline gsl_constexpr auto size(T const (&)[N]) gsl_noexcept -> size_t {\n        return N;\n    }\n\n    template <class C>\n    gsl_NODISCARD inline gsl_constexpr auto size(C const &cont) -> decltype(cont.size()) {\n        return cont.size();\n    }\n\n    template <class T, size_t N>\n    gsl_NODISCARD gsl_api inline gsl_constexpr auto data(T (&arr)[N]) gsl_noexcept -> T * {\n        return &arr[0];\n    }\n\n    template <class C>\n    gsl_NODISCARD inline gsl_constexpr auto data(C &cont) -> decltype(cont.data()) {\n        return cont.data();\n    }\n\n    template <class C>\n    gsl_NODISCARD inline gsl_constexpr auto data(C const &cont) -> decltype(cont.data()) {\n        return cont.data();\n    }\n\n    template <class E>\n    gsl_NODISCARD inline gsl_constexpr auto data(std::initializer_list<E> il) gsl_noexcept\n            -> E const * {\n        return il.begin();\n    }\n\n#endif  // gsl_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR )\n\n    }  // namespace std17\n\n    // C++20 emulation:\n\n    namespace std20 {\n\n#if gsl_CPP11_100\n\n    struct identity {\n        template <class T>\n        gsl_constexpr T &&operator()(T &&arg) const gsl_noexcept {\n            return std::forward<T>(arg);\n        }\n    };\n\n#if gsl_HAVE(ENUM_CLASS)\n    enum class endian {\n#if defined(_WIN32)\n        little = 0,\n        big = 1,\n        native = little\n#elif gsl_COMPILER_GNUC_VERSION || gsl_COMPILER_CLANG_VERSION || gsl_COMPILER_APPLECLANG_VERSION\n        little = __ORDER_LITTLE_ENDIAN__,\n        big = __ORDER_BIG_ENDIAN__,\n        native = __BYTE_ORDER__\n#elif gsl_COMPILER_ARMCC_VERSION\n        // from <endian.h> header file\n        little = __LITTLE_ENDIAN,\n        big = __BIG_ENDIAN,\n        native = __BYTE_ORDER\n#else\n// Do not define any endianness constants for unknown compilers.\n#endif\n    };\n#endif  // gsl_HAVE( ENUM_CLASS )\n\n#endif  // gsl_CPP11_100\n\n    template <class T>\n    struct type_identity {\n        typedef T type;\n    };\n#if gsl_HAVE(ALIAS_TEMPLATE)\n    template <class T>\n    using type_identity_t = typename type_identity<T>::type;\n#endif  // gsl_HAVE( ALIAS_TEMPLATE )\n\n#if gsl_HAVE(STD_SSIZE)\n\n    using std::ssize;\n\n#elif gsl_HAVE(CONSTRAINED_SPAN_CONTAINER_CTOR)\n\n    template <class C>\n    gsl_NODISCARD gsl_constexpr auto ssize(C const &c) ->\n            typename std::common_type<std::ptrdiff_t,\n                                      typename std::make_signed<decltype(c.size())>::type>::type {\n        using R = typename std::common_type<\n                std::ptrdiff_t, typename std::make_signed<decltype(c.size())>::type>::type;\n        return static_cast<R>(c.size());\n    }\n\n    template <class T, std::size_t N>\n    gsl_NODISCARD gsl_constexpr auto ssize(T const (&)[N]) gsl_noexcept -> std::ptrdiff_t {\n        return std::ptrdiff_t(N);\n    }\n\n#endif  // gsl_HAVE( STD_SSIZE )\n\n#if gsl_HAVE(REMOVE_CVREF)\n\n    using std::remove_cvref;\n\n#else\n\n    template <class T>\n    struct remove_cvref {\n        typedef typename std11::remove_cv<typename std11::remove_reference<T>::type>::type type;\n    };\n\n#endif  // gsl_HAVE( REMOVE_CVREF )\n\n    }  // namespace std20\n\n    namespace detail {\n\n    /// for gsl_ENABLE_IF_()\n\n    /*enum*/ class enabler {};\n\n#if gsl_HAVE(TYPE_TRAITS)\n\n    template <class Q>\n    struct is_span_oracle : std::false_type {};\n\n    template <class T>\n    struct is_span_oracle<span<T>> : std::true_type {};\n\n    template <class Q>\n    struct is_span : is_span_oracle<typename std::remove_cv<Q>::type> {};\n\n    template <class Q>\n    struct is_std_array_oracle : std::false_type {};\n\n#if gsl_HAVE(ARRAY)\n\n    template <class T, std::size_t Extent>\n    struct is_std_array_oracle<std::array<T, Extent>> : std::true_type {};\n\n#endif\n\n    template <class Q>\n    struct is_std_array : is_std_array_oracle<typename std::remove_cv<Q>::type> {};\n\n    template <class Q>\n    struct is_array : std::false_type {};\n\n    template <class T>\n    struct is_array<T[]> : std::true_type {};\n\n    template <class T, std::size_t N>\n    struct is_array<T[N]> : std::true_type {};\n\n#if gsl_CPP11_140 && !gsl_BETWEEN(gsl_COMPILER_GNUC_VERSION, 1, 500)\n\n    template <class, class = void>\n    struct has_size_and_data : std::false_type {};\n\n    template <class C>\n    struct has_size_and_data<C,\n                             std17::void_t<decltype(std17::size(std::declval<C>())),\n                                           decltype(std17::data(std::declval<C>()))>>\n            : std::true_type {};\n\n    template <class, class, class = void>\n    struct is_compatible_element : std::false_type {};\n\n    template <class C, class E>\n    struct is_compatible_element<C,\n                                 E,\n                                 std17::void_t<decltype(std17::data(std::declval<C>())),\n                                               typename std::remove_pointer<decltype(std17::data(\n                                                       std::declval<C &>()))>::type (*)[]>>\n            : std::is_convertible<typename std::remove_pointer<decltype(std17::data(\n                                          std::declval<C &>()))>::type (*)[],\n                                  E (*)[]> {};\n\n    template <class C>\n    struct is_container\n            : std17::bool_constant<!is_span<C>::value && !is_array<C>::value &&\n                                   !is_std_array<C>::value && has_size_and_data<C>::value> {};\n\n    template <class C, class E>\n    struct is_compatible_container\n            : std17::bool_constant<is_container<C>::value && is_compatible_element<C, E>::value> {};\n\n#else  // ^^^ gsl_CPP11_140 && ! gsl_BETWEEN( gsl_COMPILER_GNUC_VERSION, 1, 500 ) \\\n        // ^^^ / vvv ! gsl_CPP11_140 || gsl_BETWEEN( gsl_COMPILER_GNUC_VERSION, 1, \\\n        // 500 ) vvv\n\n    template <\n            class C,\n            class E,\n            typename = typename std::enable_if<\n                    !is_span<C>::value && !is_array<C>::value && !is_std_array<C>::value &&\n                            (std::is_convertible<typename std::remove_pointer<decltype(std17::data(\n                                                         std::declval<C &>()))>::type (*)[],\n                                                 E (*)[]>::value)\n                    //  &&   has_size_and_data< C >::value\n                    ,\n                    enabler>::type,\n            class = decltype(std17::size(std::declval<C>())),\n            class = decltype(std17::data(std::declval<C>()))>\n#if gsl_BETWEEN(gsl_COMPILER_MSVC_VERSION, 1, 140)\n    // VS2013 has insufficient support for expression SFINAE; we cannot make\n    // `is_compatible_container<>` a proper type trait here\n    struct is_compatible_container : std::true_type {\n    };\n#else\n    struct is_compatible_container_r {\n        is_compatible_container_r(int);\n    };\n    template <class C, class E>\n    std::true_type is_compatible_container_f(is_compatible_container_r<C, E>);\n    template <class C, class E>\n    std::false_type is_compatible_container_f(...);\n\n    template <class C, class E>\n    struct is_compatible_container : decltype(is_compatible_container_f<C, E>(0)) {};\n#endif  // gsl_BETWEEN( gsl_COMPILER_MSVC_VERSION, 1, 140 )\n\n#endif  // gsl_CPP11_140 && ! gsl_BETWEEN( gsl_COMPILER_GNUC_VERSION, 1, 500 )\n\n#endif  // gsl_HAVE( TYPE_TRAITS )\n\n    }  // namespace detail\n\n    //\n    // GSL.util: utilities\n    //\n\n    // Integer type for indices (e.g. in a loop).\n    typedef gsl_CONFIG_INDEX_TYPE index;\n\n    // Integer type for dimensions.\n    typedef gsl_CONFIG_INDEX_TYPE dim;\n\n    // Integer type for array strides.\n    typedef gsl_CONFIG_INDEX_TYPE stride;\n\n    // Integer type for pointer, iterator, or index differences.\n    typedef gsl_CONFIG_INDEX_TYPE diff;\n\n//\n// GSL.owner: ownership pointers\n//\n#if gsl_HAVE(SHARED_PTR)\n    using std::make_shared;\n    using std::shared_ptr;\n    using std::unique_ptr;\n#if gsl_HAVE(MAKE_UNIQUE) || gsl_HAVE(VARIADIC_TEMPLATE)\n    using std14::make_unique;\n#endif\n#endif\n\n#if gsl_HAVE(ALIAS_TEMPLATE)\n    template <class T\n#if gsl_HAVE(TYPE_TRAITS)\n              ,\n              typename = typename std::enable_if<std::is_pointer<T>::value>::type\n#endif\n              >\n    using owner = T;\n#elif gsl_CONFIG(DEFAULTS_VERSION) == 0\n    // TODO vNext: remove\n    template <class T>\n    struct owner {\n        typedef T type;\n    };\n#endif\n\n#define gsl_HAVE_OWNER_TEMPLATE gsl_HAVE_ALIAS_TEMPLATE\n#define gsl_HAVE_OWNER_TEMPLATE_() gsl_HAVE_OWNER_TEMPLATE\n\n// TODO vNext: remove\n#if gsl_FEATURE(OWNER_MACRO)\n#if gsl_HAVE(OWNER_TEMPLATE)\n#define Owner(t) ::gsl::owner<t>\n#else\n#define Owner(t) ::gsl::owner<t>::type\n#endif\n#endif\n\n    //\n    // GSL.assert: assertions\n    //\n\n#if gsl_HAVE(TYPE_TRAITS)\n#define gsl_ELIDE_(x)                                                \\\n    static_assert(::std::is_constructible<bool, decltype(x)>::value, \\\n                  \"argument of contract check must be convertible to bool\")\n#else\n#define gsl_ELIDE_(x)\n#endif\n#define gsl_NO_OP_() (static_cast<void>(0))\n\n#if defined(gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME)\n#if defined(__CUDACC__) && defined(__CUDA_ARCH__)\n#if gsl_COMPILER_NVCC_VERSION >= 113\n#define gsl_ASSUME_(x) ((x) ? static_cast<void>(0) : __builtin_unreachable())\n#define gsl_ASSUME_UNREACHABLE_() __builtin_unreachable()\n#else\n#define gsl_ASSUME_(x) gsl_ELIDE_(x) /* there is no assume intrinsic in CUDA device code */\n#define gsl_ASSUME_UNREACHABLE_() \\\n    gsl_NO_OP_() /* there is no assume intrinsic in CUDA device code */\n#endif\n#elif gsl_COMPILER_MSVC_VERSION >= 140\n#define gsl_ASSUME_(x) __assume(x)\n#define gsl_ASSUME_UNREACHABLE_() __assume(0)\n#elif gsl_COMPILER_GNUC_VERSION\n#define gsl_ASSUME_(x) ((x) ? static_cast<void>(0) : __builtin_unreachable())\n#define gsl_ASSUME_UNREACHABLE_() __builtin_unreachable()\n#elif defined(__has_builtin)\n#if __has_builtin(__builtin_unreachable)\n#define gsl_ASSUME_(x) ((x) ? static_cast<void>(0) : __builtin_unreachable())\n#define gsl_ASSUME_UNREACHABLE_() __builtin_unreachable()\n#else\n#error gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME: gsl-lite does not know how to generate UB optimization hints for this compiler; use gsl_CONFIG_UNENFORCED_CONTRACTS_ELIDE instead\n#endif\n#else\n#error gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME: gsl-lite does not know how to generate UB optimization hints for this compiler; use gsl_CONFIG_UNENFORCED_CONTRACTS_ELIDE instead\n#endif\n#endif  // defined( gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME )\n\n#if defined(gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME)\n#define gsl_CONTRACT_UNENFORCED_(x) gsl_ASSUME_(x)\n#else  // defined( gsl_CONFIG_UNENFORCED_CONTRACTS_ELIDE ) [default]\n#define gsl_CONTRACT_UNENFORCED_(x) gsl_ELIDE_(x)\n#endif\n\n#if defined(gsl_CONFIG_CONTRACT_VIOLATION_TRAPS)\n#if defined(__CUDACC__) && defined(__CUDA_ARCH__)\n#define gsl_TRAP_() __trap()\n#elif gsl_COMPILER_MSVC_VERSION >= 110  // __fastfail() supported by VS 2012 and later\n#define gsl_TRAP_() \\\n    __fastfail(0) /* legacy failure code for buffer-overrun errors, cf. winnt.h, \\\n                   \"Fast fail failure codes\" */\n#elif gsl_COMPILER_GNUC_VERSION\n#define gsl_TRAP_() __builtin_trap()\n#elif defined(__has_builtin)\n#if __has_builtin(__builtin_trap)\n#define gsl_TRAP_() __builtin_trap()\n#else\n#error gsl_CONFIG_CONTRACT_VIOLATION_TRAPS: gsl-lite does not know how to generate a trap instruction for this compiler; use gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES instead\n#endif\n#else\n#error gsl_CONFIG_CONTRACT_VIOLATION_TRAPS: gsl-lite does not know how to generate a trap instruction for this compiler; use gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES instead\n#endif\n#endif  // defined( gsl_CONFIG_CONTRACT_VIOLATION_TRAPS )\n\n#if defined(gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER)\n#define gsl_CONTRACT_CHECK_(str, x) \\\n    ((x) ? static_cast<void>(0) : ::gsl::fail_fast_assert_handler(#x, str, __FILE__, __LINE__))\n#if defined(__CUDACC__) && defined(__CUDA_ARCH__)\n#define gsl_FAILFAST_()                                                       \\\n    (::gsl::fail_fast_assert_handler(\"\", \"GSL: failure\", __FILE__, __LINE__), \\\n     gsl_TRAP_()) /* do not let the custom assertion handler continue execution  \\\n                 */\n#else\n#define gsl_FAILFAST_()                                                       \\\n    (::gsl::fail_fast_assert_handler(\"\", \"GSL: failure\", __FILE__, __LINE__), \\\n     ::gsl::detail::fail_fast_terminate()) /* do not let the custom assertion    \\\n                                            handler continue execution */\n#endif\n#elif defined(__CUDACC__) && defined(__CUDA_ARCH__)\n#if defined(gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS) || !defined(NDEBUG)\n#define gsl_CONTRACT_CHECK_(str, x) assert(str && (x))\n#else\n#define gsl_CONTRACT_CHECK_(str, x) ((x) ? static_cast<void>(0) : __trap())\n#endif\n#define gsl_FAILFAST_() (__trap())\n#elif defined(gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS)\n#define gsl_CONTRACT_CHECK_(str, x) assert(str && (x))\n#if !defined(NDEBUG)\n#define gsl_FAILFAST_() (assert(!\"GSL: failure\"), ::gsl::detail::fail_fast_terminate())\n#else\n#define gsl_FAILFAST_() (::gsl::detail::fail_fast_terminate())\n#endif\n#elif defined(gsl_CONFIG_CONTRACT_VIOLATION_TRAPS)\n#define gsl_CONTRACT_CHECK_(str, x) ((x) ? static_cast<void>(0) : gsl_TRAP_())\n#if gsl_COMPILER_MSVC_VERSION\n#define gsl_FAILFAST_() (gsl_TRAP_(), ::gsl::detail::fail_fast_terminate())\n#else\n#define gsl_FAILFAST_() (gsl_TRAP_())\n#endif\n#elif defined(gsl_CONFIG_CONTRACT_VIOLATION_THROWS)\n#define gsl_CONTRACT_CHECK_(str, x)                                     \\\n    ((x) ? static_cast<void>(0)                                         \\\n         : ::gsl::detail::fail_fast_throw(str \": '\" #x \"' at \" __FILE__ \\\n                                              \":\" gsl_STRINGIFY(__LINE__)))\n#define gsl_FAILFAST_() \\\n    (::gsl::detail::fail_fast_throw(\"GSL: failure at \" __FILE__ \":\" gsl_STRINGIFY(__LINE__)))\n#else  // defined( gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES ) [default]\n#define gsl_CONTRACT_CHECK_(str, x) \\\n    ((x) ? static_cast<void>(0) : ::gsl::detail::fail_fast_terminate())\n#define gsl_FAILFAST_() (::gsl::detail::fail_fast_terminate())\n#endif\n\n#if defined(gsl_CONFIG_CONTRACT_CHECKING_OFF) || defined(gsl_CONFIG_CONTRACT_CHECKING_EXPECTS_OFF)\n#define gsl_Expects(x) gsl_CONTRACT_UNENFORCED_(x)\n#else\n#define gsl_Expects(x) gsl_CONTRACT_CHECK_(\"GSL: Precondition failure\", x)\n#endif\n#define Expects(x) gsl_Expects(x)\n#if !defined(gsl_CONFIG_CONTRACT_CHECKING_AUDIT) || \\\n        defined(gsl_CONFIG_CONTRACT_CHECKING_EXPECTS_OFF)\n#define gsl_ExpectsAudit(x) gsl_ELIDE_(x)\n#else\n#define gsl_ExpectsAudit(x) gsl_CONTRACT_CHECK_(\"GSL: Precondition failure (audit)\", x)\n#endif\n\n#if defined(gsl_CONFIG_CONTRACT_CHECKING_OFF) || defined(gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF)\n#define gsl_Ensures(x) gsl_CONTRACT_UNENFORCED_(x)\n#else\n#define gsl_Ensures(x) gsl_CONTRACT_CHECK_(\"GSL: Postcondition failure\", x)\n#endif\n#define Ensures(x) gsl_Ensures(x)\n#if !defined(gsl_CONFIG_CONTRACT_CHECKING_AUDIT) || \\\n        defined(gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF)\n#define gsl_EnsuresAudit(x) gsl_ELIDE_(x)\n#else\n#define gsl_EnsuresAudit(x) gsl_CONTRACT_CHECK_(\"GSL: Postcondition failure (audit)\", x)\n#endif\n\n#if defined(gsl_CONFIG_CONTRACT_CHECKING_OFF) || defined(gsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF)\n#define gsl_Assert(x) gsl_CONTRACT_UNENFORCED_(x)\n#else\n#define gsl_Assert(x) gsl_CONTRACT_CHECK_(\"GSL: Assertion failure\", x)\n#endif\n#if !defined(gsl_CONFIG_CONTRACT_CHECKING_AUDIT) || defined(gsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF)\n#define gsl_AssertAudit(x) gsl_ELIDE_(x)\n#else\n#define gsl_AssertAudit(x) gsl_CONTRACT_CHECK_(\"GSL: Assertion failure (audit)\", x)\n#endif\n\n#define gsl_FailFast() gsl_FAILFAST_()\n\n    struct fail_fast : public std::logic_error {\n        explicit fail_fast(char const *message) : std::logic_error(message) {}\n    };\n\n    namespace detail {\n\n#if gsl_HAVE(EXCEPTIONS)\n    gsl_NORETURN inline void fail_fast_throw(char const *message) { throw fail_fast(message); }\n#endif  // gsl_HAVE( EXCEPTIONS )\n    gsl_NORETURN inline void fail_fast_terminate() gsl_noexcept { std::terminate(); }\n\n    }  // namespace detail\n\n    // Should be defined by user\n    gsl_api void fail_fast_assert_handler(char const *const expression, char const *const message,\n                                          char const *const file, int line);\n\n#if defined(gsl_CONFIG_CONTRACT_VIOLATION_THROWS)\n\n#if gsl_HAVE(EXCEPTIONS)\n    gsl_DEPRECATED_MSG(\n            \"don't call gsl::fail_fast_assert() directly; use \"\n            \"contract checking macros instead\") gsl_constexpr14 inline void\n    fail_fast_assert(bool cond, char const *const message) {\n        if (!cond)\n            throw fail_fast(message);\n    }\n#endif  // gsl_HAVE( EXCEPTIONS )\n\n#elif defined(gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER)\n\n    gsl_DEPRECATED_MSG(\n            \"don't call gsl::fail_fast_assert() directly; use \"\n            \"contract checking macros instead\") gsl_api gsl_constexpr14 inline void\n    fail_fast_assert(bool cond, char const *const expression, char const *const message,\n                     char const *const file, int line) {\n        if (!cond)\n            ::gsl::fail_fast_assert_handler(expression, message, file, line);\n    }\n\n#else  // defined( gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES ) [default]\n\n    gsl_DEPRECATED_MSG(\n            \"don't call gsl::fail_fast_assert() directly; use \"\n            \"contract checking macros instead\") gsl_constexpr14 inline void\n    fail_fast_assert(bool cond) gsl_noexcept {\n        if (!cond)\n            std::terminate();\n    }\n\n#endif\n\n    //\n    // GSL.util: utilities\n    //\n\n#if gsl_FEATURE(EXPERIMENTAL_RETURN_GUARD)\n\n    // Add uncaught_exceptions for pre-2017 MSVC, GCC and Clang\n    // Return unsigned char to save stack space, uncaught_exceptions can only\n    // increase by 1 in a scope\n\n    namespace std11 {\n\n#if gsl_HAVE(UNCAUGHT_EXCEPTIONS)\n\n    inline unsigned char uncaught_exceptions() gsl_noexcept {\n        return static_cast<unsigned char>(std::uncaught_exceptions());\n    }\n\n#else                  // ! gsl_HAVE( UNCAUGHT_EXCEPTIONS )\n#if defined(_MSC_VER)  // MS-STL with either MSVC or clang-cl\n\n    inline unsigned char uncaught_exceptions() gsl_noexcept {\n        return static_cast<unsigned char>(*reinterpret_cast<unsigned const *>(\n                detail::_getptd() + (sizeof(void *) == 8 ? 0x100 : 0x90)));\n    }\n\n#elif gsl_COMPILER_CLANG_VERSION || gsl_COMPILER_GNUC_VERSION || gsl_COMPILER_APPLECLANG_VERSION\n\n    inline unsigned char uncaught_exceptions() gsl_noexcept {\n        return static_cast<unsigned char>((*reinterpret_cast<unsigned const *>(\n                reinterpret_cast<unsigned char const *>(detail::__cxa_get_globals()) +\n                sizeof(void *))));\n    }\n\n#endif\n#endif\n\n    }  // namespace std11\n\n#endif  // gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD )\n\n#if gsl_STDLIB_CPP11_110\n\n    gsl_DISABLE_MSVC_WARNINGS(4702)  // unreachable code\n\n            template <class F>\n            class final_action {\n    public:\n        explicit final_action(F action) gsl_noexcept : action_(std::move(action)), invoke_(true) {}\n\n        final_action(final_action &&other) gsl_noexcept : action_(std::move(other.action_)),\n                                                          invoke_(other.invoke_) {\n            other.invoke_ = false;\n        }\n\n        gsl_SUPPRESS_MSGSL_WARNING(f .6) virtual ~final_action() gsl_noexcept {\n            if (invoke_)\n                action_();\n        }\n\n        gsl_is_delete_access : final_action(final_action const &) gsl_is_delete;\n        final_action &operator=(final_action const &) gsl_is_delete;\n        final_action &operator=(final_action &&) gsl_is_delete;\n\n    protected:\n        void dismiss() gsl_noexcept { invoke_ = false; }\n\n    private:\n        F action_;\n        bool invoke_;\n    };\n\n    template <class F>\n    gsl_NODISCARD inline final_action<F> finally(F const &action) gsl_noexcept {\n        return final_action<F>(action);\n    }\n\n    template <class F>\n    gsl_NODISCARD inline final_action<F> finally(F && action) gsl_noexcept {\n        return final_action<F>(std::forward<F>(action));\n    }\n\n#if gsl_FEATURE(EXPERIMENTAL_RETURN_GUARD)\n\n    template <class F>\n    class final_action_return : public final_action<F> {\n    public:\n        explicit final_action_return(F &&action) gsl_noexcept\n                : final_action<F>(std::move(action)),\n                  exception_count(std11::uncaught_exceptions()) {}\n\n        final_action_return(final_action_return &&other) gsl_noexcept\n                : final_action<F>(std::move(other)),\n                  exception_count(std11::uncaught_exceptions()) {}\n\n        ~final_action_return() override {\n            if (std11::uncaught_exceptions() != exception_count)\n                this->dismiss();\n        }\n\n        gsl_is_delete_access : final_action_return(final_action_return const &) gsl_is_delete;\n        final_action_return &operator=(final_action_return const &) gsl_is_delete;\n\n    private:\n        unsigned char exception_count;\n    };\n\n    template <class F>\n    gsl_NODISCARD inline final_action_return<F> on_return(F const &action) gsl_noexcept {\n        return final_action_return<F>(action);\n    }\n\n    template <class F>\n    gsl_NODISCARD inline final_action_return<F> on_return(F && action) gsl_noexcept {\n        return final_action_return<F>(std::forward<F>(action));\n    }\n\n    template <class F>\n    class final_action_error : public final_action<F> {\n    public:\n        explicit final_action_error(F &&action) gsl_noexcept\n                : final_action<F>(std::move(action)),\n                  exception_count(std11::uncaught_exceptions()) {}\n\n        final_action_error(final_action_error &&other) gsl_noexcept\n                : final_action<F>(std::move(other)),\n                  exception_count(std11::uncaught_exceptions()) {}\n\n        ~final_action_error() override {\n            if (std11::uncaught_exceptions() == exception_count)\n                this->dismiss();\n        }\n\n        gsl_is_delete_access : final_action_error(final_action_error const &) gsl_is_delete;\n        final_action_error &operator=(final_action_error const &) gsl_is_delete;\n\n    private:\n        unsigned char exception_count;\n    };\n\n    template <class F>\n    gsl_NODISCARD inline final_action_error<F> on_error(F const &action) gsl_noexcept {\n        return final_action_error<F>(action);\n    }\n\n    template <class F>\n    gsl_NODISCARD inline final_action_error<F> on_error(F && action) gsl_noexcept {\n        return final_action_error<F>(std::forward<F>(action));\n    }\n\n#endif  // gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD )\n\n    gsl_RESTORE_MSVC_WARNINGS()\n#else  // ! gsl_STDLIB_CPP11_110\n\n    class final_action {\n    public:\n        typedef void (*Action)();\n\n        final_action(Action action) : action_(action), invoke_(true) {}\n\n        final_action(final_action const &other) : action_(other.action_), invoke_(other.invoke_) {\n            other.invoke_ = false;\n        }\n\n        virtual ~final_action() {\n            if (invoke_)\n                action_();\n        }\n\n    protected:\n        void dismiss() { invoke_ = false; }\n\n    private:\n        final_action &operator=(final_action const &);\n\n    private:\n        Action action_;\n        mutable bool invoke_;\n    };\n\n    template <class F>\n    inline final_action finally(F const &f) {\n        return final_action((f));\n    }\n\n#if gsl_FEATURE(EXPERIMENTAL_RETURN_GUARD)\n\n    class final_action_return : public final_action {\n    public:\n        explicit final_action_return(Action action)\n                : final_action(action), exception_count(std11::uncaught_exceptions()) {}\n\n        ~final_action_return() {\n            if (std11::uncaught_exceptions() != exception_count)\n                this->dismiss();\n        }\n\n    private:\n        final_action_return &operator=(final_action_return const &);\n\n    private:\n        unsigned char exception_count;\n    };\n\n    template <class F>\n    inline final_action_return on_return(F const &action) {\n        return final_action_return(action);\n    }\n\n    class final_action_error : public final_action {\n    public:\n        explicit final_action_error(Action action)\n                : final_action(action), exception_count(std11::uncaught_exceptions()) {}\n\n        ~final_action_error() {\n            if (std11::uncaught_exceptions() == exception_count)\n                this->dismiss();\n        }\n\n    private:\n        final_action_error &operator=(final_action_error const &);\n\n    private:\n        unsigned char exception_count;\n    };\n\n    template <class F>\n    inline final_action_error on_error(F const &action) {\n        return final_action_error(action);\n    }\n\n#endif  // gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD )\n\n#endif  // gsl_STDLIB_CPP11_110\n\n#if gsl_STDLIB_CPP11_120\n\n            template <class T, class U>\n            gsl_NODISCARD gsl_api inline gsl_constexpr T narrow_cast(U && u) gsl_noexcept {\n        return static_cast<T>(std::forward<U>(u));\n    }\n\n#else  // ! gsl_STDLIB_CPP11_120\n\n    template <class T, class U>\n    gsl_api inline T narrow_cast(U u) gsl_noexcept {\n        return static_cast<T>(u);\n    }\n\n#endif  // gsl_STDLIB_CPP11_120\n\n    struct narrowing_error : public std::exception {\n        char const *what() const gsl_noexcept\n#if gsl_HAVE(OVERRIDE_FINAL)\n                override\n#endif\n        {\n            return \"narrowing_error\";\n        }\n    };\n\n#if gsl_HAVE(TYPE_TRAITS)\n\n    namespace detail {\n\n    template <class T, class U>\n    struct is_same_signedness\n            : public std::integral_constant<bool,\n                                            std::is_signed<T>::value == std::is_signed<U>::value> {\n    };\n\n#if gsl_COMPILER_NVCC_VERSION\n    // We do this to circumvent NVCC warnings about pointless unsigned comparisons\n    // with 0.\n    template <class T>\n    gsl_constexpr gsl_api bool is_negative(T value, std::true_type /*isSigned*/) gsl_noexcept {\n        return value < T();\n    }\n    template <class T>\n    gsl_constexpr gsl_api bool is_negative(T /*value*/,\n                                           std::false_type /*isUnsigned*/) gsl_noexcept {\n        return false;\n    }\n    template <class T, class U>\n    gsl_constexpr gsl_api bool have_same_sign(T,\n                                              U,\n                                              std::true_type /*isSameSignedness*/) gsl_noexcept {\n        return true;\n    }\n    template <class T, class U>\n    gsl_constexpr gsl_api bool have_same_sign(T t,\n                                              U u,\n                                              std::false_type /*isSameSignedness*/) gsl_noexcept {\n        return detail::is_negative(t, std::is_signed<T>()) ==\n               detail::is_negative(u, std::is_signed<U>());\n    }\n#endif  // gsl_COMPILER_NVCC_VERSION\n\n    }  // namespace detail\n\n#endif\n\n    template <class T, class U>\n    gsl_NODISCARD\n#if !gsl_CONFIG(NARROW_THROWS_ON_TRUNCATION) && !defined(gsl_CONFIG_CONTRACT_VIOLATION_THROWS)\n            gsl_api\n#endif\n            inline T\n            narrow(U u) {\n#if gsl_CONFIG(NARROW_THROWS_ON_TRUNCATION) && !gsl_HAVE(EXCEPTIONS)\n        gsl_STATIC_ASSERT_(detail::dependent_false<T>::value,\n                           \"According to the GSL specification, narrow<>() throws an exception of \"\n                           \"type narrowing_error on truncation. Therefore \"\n                           \"it cannot be used if exceptions are disabled. Consider using \"\n                           \"narrow_failfast<>() instead which raises a precondition \"\n                           \"violation if the given value cannot be represented in the target \"\n                           \"type.\");\n#endif\n\n        T t = static_cast<T>(u);\n\n        if (static_cast<U>(t) != u) {\n#if gsl_HAVE(EXCEPTIONS) && \\\n        (gsl_CONFIG(NARROW_THROWS_ON_TRUNCATION) || defined(gsl_CONFIG_CONTRACT_VIOLATION_THROWS))\n            throw narrowing_error();\n#else\n            std::terminate();\n#endif\n        }\n\n#if gsl_HAVE(TYPE_TRAITS)\n#if gsl_COMPILER_NVCC_VERSION\n        if (!detail::have_same_sign(t, u, detail::is_same_signedness<T, U>()))\n#else\n        gsl_SUPPRESS_MSVC_WARNING(\n                4127,\n                \"conditional expression is constant\") if (!detail::is_same_signedness<T,\n                                                                                      U>::value &&\n                                                          (t < T()) != (u < U()))\n#endif\n#else\n        // Don't assume T() works:\n        gsl_SUPPRESS_MSVC_WARNING(4127,\n                                  \"conditional expression is constant\") if ((t < 0) != (u < 0))\n#endif\n        {\n#if gsl_HAVE(EXCEPTIONS) && \\\n        (gsl_CONFIG(NARROW_THROWS_ON_TRUNCATION) || defined(gsl_CONFIG_CONTRACT_VIOLATION_THROWS))\n            throw narrowing_error();\n#else\n            std::terminate();\n#endif\n        }\n\n        return t;\n    }\n\n    template <class T, class U>\n    gsl_NODISCARD gsl_api inline T narrow_failfast(U u) {\n        T t = static_cast<T>(u);\n\n        gsl_Expects(static_cast<U>(t) == u);\n\n#if gsl_HAVE(TYPE_TRAITS)\n#if gsl_COMPILER_NVCC_VERSION\n        gsl_Expects(::gsl::detail::have_same_sign(t, u, ::gsl::detail::is_same_signedness<T, U>()));\n#else\n        gsl_SUPPRESS_MSVC_WARNING(4127, \"conditional expression is constant\") gsl_Expects(\n                (::gsl::detail::is_same_signedness<T, U>::value || (t < T()) == (u < U())));\n#endif\n#else\n        // Don't assume T() works:\n        gsl_SUPPRESS_MSVC_WARNING(4127, \"conditional expression is constant\")\n                gsl_Expects((t < 0) == (u < 0));\n#endif\n\n        return t;\n    }\n\n    //\n    // at() - Bounds-checked way of accessing static arrays, std::array,\n    // std::vector.\n    //\n\n    template <class T, size_t N>\n    gsl_NODISCARD gsl_api inline gsl_constexpr14 T &at(T(&arr)[N], size_t pos) {\n        gsl_Expects(pos < N);\n        return arr[pos];\n    }\n\n    template <class Container>\n    gsl_NODISCARD gsl_api inline gsl_constexpr14 typename Container::value_type &at(\n            Container & cont, size_t pos) {\n        gsl_Expects(pos < cont.size());\n        return cont[pos];\n    }\n\n    template <class Container>\n    gsl_NODISCARD gsl_api inline gsl_constexpr14 typename Container::value_type const &at(\n            Container const &cont, size_t pos) {\n        gsl_Expects(pos < cont.size());\n        return cont[pos];\n    }\n\n#if gsl_HAVE(INITIALIZER_LIST)\n\n    template <class T>\n    gsl_NODISCARD gsl_api inline const gsl_constexpr14 T at(std::initializer_list<T> cont,\n                                                            size_t pos) {\n        gsl_Expects(pos < cont.size());\n        return *(cont.begin() + pos);\n    }\n#endif\n\n    template <class T>\n    gsl_NODISCARD gsl_api inline gsl_constexpr14 T &at(span<T> s, size_t pos) {\n        return s[pos];\n    }\n\n    //\n    // GSL.views: views\n    //\n\n    //\n    // not_null<> - Wrap any indirection and enforce non-null.\n    //\n\n    template <class T>\n    class not_null;\n\n    namespace detail {\n\n// helper class to figure out the pointed-to type of a pointer\n#if gsl_STDLIB_CPP11_OR_GREATER\n    template <class T, class E = void>\n    struct element_type_helper {\n        // For types without a member element_type (this will handle raw pointers)\n        typedef typename std::remove_reference<decltype(*std::declval<T>())>::type type;\n    };\n\n    template <class T>\n    struct element_type_helper<T, std17::void_t<typename T::element_type>> {\n        // For types with a member element_type\n        typedef typename T::element_type type;\n    };\n#else   // ! gsl_STDLIB_CPP11_OR_GREATER\n    // Pre-C++11, we cannot have decltype, so we cannot handle types without a\n    // member element_type\n    template <class T, class E = void>\n    struct element_type_helper {\n        typedef typename T::element_type type;\n    };\n\n    template <class T>\n    struct element_type_helper<T *> {\n        typedef T type;\n    };\n#endif  // gsl_STDLIB_CPP11_OR_GREATER\n\n    template <class T>\n    struct is_not_null_or_bool_oracle : std11::false_type {};\n    template <class T>\n    struct is_not_null_or_bool_oracle<not_null<T>> : std11::true_type {};\n    template <>\n    struct is_not_null_or_bool_oracle<bool> : std11::true_type {};\n\n    template <class T, bool IsCopyable = true>\n    struct not_null_data;\n#if gsl_HAVE(MOVE_FORWARD)\n    template <class T>\n    struct not_null_data<T, false> {\n        T ptr_;\n\n        gsl_api gsl_constexpr14 not_null_data(T &&_ptr) gsl_noexcept : ptr_(std::move(_ptr)) {}\n\n        gsl_api gsl_constexpr14 not_null_data(not_null_data &&other) gsl_noexcept\n                : ptr_(std::move(other.ptr_)) {}\n        gsl_api gsl_constexpr14 not_null_data &operator=(not_null_data &&other) gsl_noexcept {\n            ptr_ = std::move(other.ptr_);\n            return *this;\n        }\n\n        gsl_is_delete_access : not_null_data(not_null_data const &) gsl_is_delete;\n        not_null_data &operator=(not_null_data const &) gsl_is_delete;\n    };\n#if gsl_CONFIG_DEFAULTS_VERSION >= 1\n#endif  // gsl_CONFIG_DEFAULTS_VERSION >= 1\n#endif  // gsl_HAVE( MOVE_FORWARD )\n    template <class T>\n    struct not_null_data<T, true> {\n        T ptr_;\n\n        gsl_api gsl_constexpr14 not_null_data(T const &_ptr) gsl_noexcept : ptr_(_ptr) {}\n\n#if gsl_HAVE(MOVE_FORWARD)\n        gsl_api gsl_constexpr14 not_null_data(T &&_ptr) gsl_noexcept : ptr_(std::move(_ptr)) {}\n\n        gsl_api gsl_constexpr14 not_null_data(not_null_data &&other) gsl_noexcept\n                : ptr_(std::move(other.ptr_)) {}\n        gsl_api gsl_constexpr14 not_null_data &operator=(not_null_data &&other) gsl_noexcept {\n            ptr_ = std::move(other.ptr_);\n            return *this;\n        }\n#endif  // gsl_HAVE( MOVE_FORWARD )\n\n        gsl_api gsl_constexpr14 not_null_data(not_null_data const &other) : ptr_(other.ptr_) {\n            gsl_Expects(ptr_ != gsl_nullptr);\n        }\n        gsl_api gsl_constexpr14 not_null_data &operator=(not_null_data const &other) {\n            gsl_Expects(other.ptr_ != gsl_nullptr);\n            ptr_ = other.ptr_;\n            return *this;\n        }\n    };\n#if gsl_CONFIG_DEFAULTS_VERSION >= 1\n    template <class T>\n    struct not_null_data<T *, true> {\n        T *ptr_;\n\n        gsl_api gsl_constexpr14 not_null_data(T *_ptr) gsl_noexcept : ptr_(_ptr) {}\n    };\n#endif  // gsl_CONFIG_DEFAULTS_VERSION >= 1\n    template <class T>\n    struct is_copyable\n#if gsl_HAVE(TYPE_TRAITS)\n            : std11::integral_constant<bool,\n                                       std::is_copy_constructible<T>::value &&\n                                               std::is_copy_assignable<T>::value>\n#else\n            : std11::true_type\n#endif\n    {\n    };\n#if gsl_HAVE(TYPE_TRAITS) && gsl_HAVE(UNIQUE_PTR) && gsl_BETWEEN(gsl_COMPILER_MSVC_VERSION, 1, 140)\n    // Type traits are buggy in VC++ 2013, so we explicitly declare `unique_ptr<>`\n    // non-copyable.\n    template <class T, class Deleter>\n    struct is_copyable<std::unique_ptr<T, Deleter>> : std11::false_type {};\n#endif\n\n    template <class T>\n    struct not_null_accessor;\n\n    }  // namespace detail\n\n    template <class T>\n    class not_null {\n    private:\n        detail::not_null_data<T, detail::is_copyable<T>::value> data_;\n\n        // need to access `not_null<U>::data_`\n        template <class U>\n        friend class not_null;\n\n        template <class U>\n        friend struct detail::not_null_accessor;\n\n    public:\n        typedef typename detail::element_type_helper<T>::type element_type;\n\n#if gsl_HAVE(TYPE_TRAITS)\n        static_assert(\n                std::is_assignable<\n                        typename std::remove_const<typename std::remove_reference<T>::type>::type &,\n                        std::nullptr_t>::value,\n                \"T cannot be assigned nullptr.\");\n#endif\n\n#if gsl_CONFIG(NOT_NULL_EXPLICIT_CTOR)\n#if gsl_HAVE(MOVE_FORWARD)\n        template <class U\n        // In Clang 3.x, `is_constructible<not_null<unique_ptr<X>>, unique_ptr<X>>`\n        // tries to instantiate the copy constructor of `unique_ptr<>`, triggering\n        // an error. Note that Apple Clang's `__clang_major__` etc. are different\n        // from regular Clang.\n#if gsl_HAVE(TYPE_TRAITS) && gsl_HAVE(DEFAULT_FUNCTION_TEMPLATE_ARG) && \\\n        !gsl_BETWEEN(gsl_COMPILER_CLANG_VERSION, 1, 400) &&             \\\n        !gsl_BETWEEN(gsl_COMPILER_APPLECLANG_VERSION, 1, 1001)\n                  // We *have* to use SFINAE with an NTTP arg here, otherwise the\n                  // overload is ambiguous.\n                  ,\n                  typename std::enable_if<(std::is_constructible<T, U>::value), int>::type = 0\n#endif\n                  >\n        gsl_api gsl_constexpr14 explicit not_null(U other) : data_(T(std::move(other))) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n#else   // a.k.a. ! gsl_HAVE( MOVE_FORWARD )\n        template <class U>\n        gsl_api gsl_constexpr14 explicit not_null(U const &other) : data_(T(other)) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n#endif  // gsl_HAVE( MOVE_FORWARD )\n#else   // a.k.a. !gsl_CONFIG( NOT_NULL_EXPLICIT_CTOR )\n#if gsl_HAVE(MOVE_FORWARD)\n        // In Clang 3.x, `is_constructible<not_null<unique_ptr<X>>, unique_ptr<X>>`\n        // tries to instantiate the copy constructor of `unique_ptr<>`, triggering\n        // an error. Note that Apple Clang's `__clang_major__` etc. are different\n        // from regular Clang.\n#if gsl_HAVE(TYPE_TRAITS) && gsl_HAVE(DEFAULT_FUNCTION_TEMPLATE_ARG) && \\\n        !gsl_BETWEEN(gsl_COMPILER_CLANG_VERSION, 1, 400) &&             \\\n        !gsl_BETWEEN(gsl_COMPILER_APPLECLANG_VERSION, 1, 1001)\n        template <class U\n                  // We *have* to use SFINAE with an NTTP arg here, otherwise the\n                  // overload is ambiguous.\n                  ,\n                  typename std::enable_if<(std::is_constructible<T, U>::value &&\n                                           !std::is_convertible<U, T>::value),\n                                          int>::type = 0>\n        gsl_api gsl_constexpr14 explicit not_null(U other) : data_(T(std::move(other))) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n\n        template <class U\n                  // We *have* to use SFINAE with an NTTP arg here, otherwise the\n                  // overload is ambiguous.\n                  ,\n                  typename std::enable_if<(std::is_convertible<U, T>::value), int>::type = 0>\n        gsl_api gsl_constexpr14 not_null(U other) : data_(std::move(other)) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n#else   // a.k.a. !( gsl_HAVE( TYPE_TRAITS ) && gsl_HAVE(         \\\n        // DEFAULT_FUNCTION_TEMPLATE_ARG ) && ! gsl_BETWEEN(      \\\n        // gsl_COMPILER_CLANG_VERSION, 1, 400 ) && ! gsl_BETWEEN( \\\n        // gsl_COMPILER_APPLECLANG_VERSION, 1, 1001 )                  \\\n        // If type_traits are not available, then we can't distinguish               \\\n        // `is_convertible<>` and `is_constructible<>`, so we unconditionally permit \\\n        // implicit construction.\n        template <class U>\n        gsl_api gsl_constexpr14 not_null(U other) : data_(T(std::move(other))) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n#endif  // gsl_HAVE( TYPE_TRAITS ) && gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) \\\n        // && ! gsl_BETWEEN( gsl_COMPILER_CLANG_VERSION, 1, 400 ) && !          \\\n        // gsl_BETWEEN( gsl_COMPILER_APPLECLANG_VERSION, 1, 1001 )\n#else   // a.k.a. ! gsl_HAVE( MOVE_FORWARD )\n        template <class U>\n        gsl_api gsl_constexpr14 not_null(U const &other) : data_(T(other)) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n#endif  // gsl_HAVE( MOVE_FORWARD )\n#endif  // gsl_CONFIG( NOT_NULL_EXPLICIT_CTOR )\n\n    public:\n#if gsl_HAVE(MOVE_FORWARD)\n        // In Clang 3.x, `is_constructible<not_null<unique_ptr<X>>, unique_ptr<X>>`\n        // tries to instantiate the copy constructor of `unique_ptr<>`, triggering\n        // an error. Note that Apple Clang's `__clang_major__` etc. are different\n        // from regular Clang.\n#if gsl_HAVE(TYPE_TRAITS) && gsl_HAVE(DEFAULT_FUNCTION_TEMPLATE_ARG) && \\\n        !gsl_BETWEEN(gsl_COMPILER_CLANG_VERSION, 1, 400) &&             \\\n        !gsl_BETWEEN(gsl_COMPILER_APPLECLANG_VERSION, 1, 1001)\n        template <class U\n                  // We *have* to use SFINAE with an NTTP arg here, otherwise the\n                  // overload is ambiguous.\n                  ,\n                  typename std::enable_if<(std::is_constructible<T, U>::value &&\n                                           !std::is_convertible<U, T>::value),\n                                          int>::type = 0>\n        gsl_api gsl_constexpr14 explicit not_null(not_null<U> other)\n                : data_(T(std::move(other.data_.ptr_))) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n\n        template <class U\n                  // We *have* to use SFINAE with an NTTP arg here, otherwise the\n                  // overload is ambiguous.\n                  ,\n                  typename std::enable_if<(std::is_convertible<U, T>::value), int>::type = 0>\n        gsl_api gsl_constexpr14 not_null(not_null<U> other)\n                : data_(T(std::move(other.data_.ptr_))) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n#else   // a.k.a. ! ( gsl_HAVE( TYPE_TRAITS ) && gsl_HAVE(        \\\n        // DEFAULT_FUNCTION_TEMPLATE_ARG ) && ! gsl_BETWEEN(      \\\n        // gsl_COMPILER_CLANG_VERSION, 1, 400 ) && ! gsl_BETWEEN( \\\n        // gsl_COMPILER_APPLECLANG_VERSION, 1, 1001 )                  \\\n        // If type_traits are not available, then we can't distinguish               \\\n        // `is_convertible<>` and `is_constructible<>`, so we unconditionally permit \\\n        // implicit construction.\n        template <class U>\n        gsl_api gsl_constexpr14 not_null(not_null<U> other)\n                : data_(T(std::move(other.data_.ptr_))) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n        template <class U>\n        gsl_api gsl_constexpr14 not_null<T> &operator=(not_null<U> other) {\n            gsl_Expects(other.data_.ptr_ != gsl_nullptr);\n            data_.ptr_ = std::move(other.data_.ptr_);\n            return *this;\n        }\n#endif  // gsl_HAVE( TYPE_TRAITS ) && gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) \\\n        // && ! gsl_BETWEEN( gsl_COMPILER_CLANG_VERSION, 1, 400 ) && !          \\\n        // gsl_BETWEEN( gsl_COMPILER_APPLECLANG_VERSION, 1, 1001 )\n#else   // a.k.a. ! gsl_HAVE( MOVE_FORWARD )\n        template <class U>\n        gsl_api gsl_constexpr14 not_null(not_null<U> const &other) : data_(T(other.data_.ptr_)) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n        template <class U>\n        gsl_api gsl_constexpr14 not_null<T> &operator=(not_null<U> const &other) {\n            gsl_Expects(other.data_.ptr_ != gsl_nullptr);\n            data_.ptr_ = other.data_.ptr_;\n            return *this;\n        }\n#endif  // gsl_HAVE( MOVE_FORWARD )\n\n#if gsl_CONFIG(TRANSPARENT_NOT_NULL)\n        gsl_NODISCARD gsl_api gsl_constexpr14 element_type *get() const {\n            element_type *result = data_.ptr_.get();\n            gsl_Ensures(result != gsl_nullptr);\n            return result;\n        }\n#else\n#if gsl_CONFIG(NOT_NULL_GET_BY_CONST_REF)\n        gsl_NODISCARD gsl_api gsl_constexpr14 T const &get() const {\n            T const &result = data_.ptr_;\n            gsl_Ensures(result != gsl_nullptr);\n            return result;\n        }\n#else\n        gsl_NODISCARD gsl_api gsl_constexpr14 T get() const {\n            T result = data_.ptr_;\n            gsl_Ensures(result != gsl_nullptr);\n            return result;\n        }\n#endif\n#endif\n\n        // We want an implicit conversion operator that can be used to convert from\n        // both lvalues (by const reference or by copy) and rvalues (by move). So it\n        // seems like we could define\n        //\n        //     template< class U >\n        //     operator U const &() const & { ... }\n        //     template< class U >\n        //     operator U &&() && { ... }\n        //\n        // However, having two conversion operators with different return types\n        // renders the assignment operator of the result type ambiguous:\n        //\n        //     not_null<std::unique_ptr<T>> p( ... );\n        //     std::unique_ptr<U> q;\n        //     q = std::move( p ); // ambiguous\n        //\n        // To avoid this ambiguity, we have both overloads of the conversion\n        // operator return `U` rather than `U const &` or `U &&`. This implies that\n        // converting an lvalue always induces a copy, which can cause unnecessary\n        // copies or even fail to compile in some situations:\n        //\n        //     not_null<std::shared_ptr<T>> sp( ... );\n        //     std::shared_ptr<U> const & rs = sp; // unnecessary copy\n        //     std::unique_ptr<U> const & ru = p; // error: cannot copy\n        //     `unique_ptr<T>`\n        //\n        // However, these situations are rather unusual, and the following, more\n        // frequent situations remain unimpaired:\n        //\n        //     std::shared_ptr<U> vs = sp; // no extra copy\n        //     std::unique_ptr<U> vu = std::move( p );\n\n#if gsl_HAVE(MOVE_FORWARD) && gsl_HAVE(TYPE_TRAITS) && gsl_HAVE(DEFAULT_FUNCTION_TEMPLATE_ARG) && \\\n        gsl_HAVE(EXPLICIT)\n        // explicit conversion operator\n\n        template <class U\n                  // We *have* to use SFINAE with an NTTP arg here, otherwise the overload\n                  // is ambiguous.\n                  ,\n                  typename std::enable_if<(std::is_constructible<U, T const &>::value &&\n                                           !std::is_convertible<T, U>::value &&\n                                           !detail::is_not_null_or_bool_oracle<U>::value),\n                                          int>::type = 0>\n        gsl_NODISCARD gsl_api gsl_constexpr14 explicit operator U() const\n#if gsl_HAVE(FUNCTION_REF_QUALIFIER)\n                &\n#endif\n        {\n            U result(data_.ptr_);\n            gsl_Ensures(result != gsl_nullptr);\n            return result;\n        }\n#if gsl_HAVE(FUNCTION_REF_QUALIFIER)\n        template <class U\n                  // We *have* to use SFINAE with an NTTP arg here, otherwise the overload\n                  // is ambiguous.\n                  ,\n                  typename std::enable_if<(std::is_constructible<U, T>::value &&\n                                           !std::is_convertible<T, U>::value &&\n                                           !detail::is_not_null_or_bool_oracle<U>::value),\n                                          int>::type = 0>\n        gsl_NODISCARD gsl_api gsl_constexpr14 explicit operator U() && {\n            U result(std::move(data_.ptr_));\n            gsl_Ensures(result != gsl_nullptr);\n            return result;\n        }\n#endif\n\n        // implicit conversion operator\n        template <class U\n                  // We *have* to use SFINAE with an NTTP arg here, otherwise the overload\n                  // is ambiguous.\n                  ,\n                  typename std::enable_if<(std::is_constructible<U, T const &>::value &&\n                                           std::is_convertible<T, U>::value &&\n                                           !detail::is_not_null_or_bool_oracle<U>::value),\n                                          int>::type = 0>\n        gsl_NODISCARD gsl_api gsl_constexpr14 operator U() const\n#if gsl_HAVE(FUNCTION_REF_QUALIFIER)\n                &\n#endif\n        {\n            U result(data_.ptr_);\n            gsl_Ensures(result != gsl_nullptr);\n            return result;\n        }\n#if gsl_HAVE(FUNCTION_REF_QUALIFIER)\n        template <class U\n                  // We *have* to use SFINAE with an NTTP arg here, otherwise the overload\n                  // is ambiguous.\n                  ,\n                  typename std::enable_if<(std::is_convertible<T, U>::value &&\n                                           !detail::is_not_null_or_bool_oracle<U>::value),\n                                          int>::type = 0>\n        gsl_NODISCARD gsl_api gsl_constexpr14 operator U() && {\n            U result(std::move(data_.ptr_));\n            gsl_Ensures(result != gsl_nullptr);\n            return result;\n        }\n#endif\n#else   // a.k.a. #if !( gsl_HAVE( MOVE_FORWARD ) && gsl_HAVE( TYPE_TRAITS ) && \\\n        // gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) && gsl_HAVE( EXPLICIT ) )\n        template <class U>\n        gsl_NODISCARD gsl_api gsl_constexpr14 operator U() const {\n            U result(data_.ptr_);\n            gsl_Ensures(result != gsl_nullptr);\n            return result;\n        }\n#endif  // gsl_HAVE( MOVE_FORWARD ) && gsl_HAVE( TYPE_TRAITS ) && gsl_HAVE( \\\n        // DEFAULT_FUNCTION_TEMPLATE_ARG ) && gsl_HAVE( EXPLICIT )\n\n        gsl_NODISCARD gsl_api gsl_constexpr14 T const &operator->() const {\n            T const &result(data_.ptr_);\n            gsl_Ensures(result != gsl_nullptr);\n            return result;\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr14 element_type &operator*() const {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n            return *data_.ptr_;\n        }\n\n#if gsl_HAVE(MOVE_FORWARD)\n        // Visual C++ 2013 doesn't generate default move constructors, so we declare\n        // them explicitly.\n        gsl_api gsl_constexpr14 not_null(not_null &&other)\n                gsl_noexcept_not_testing  // we want to be nothrow-movable despite the\n                                          // precondition check\n                : data_(std::move(other.data_)) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n        gsl_api gsl_constexpr14 not_null &operator=(not_null &&other)\n                gsl_noexcept_not_testing  // we want to be nothrow-movable despite the\n                                          // precondition check\n        {\n            gsl_Expects(other.data_.ptr_ != gsl_nullptr || &other == this);\n            data_ = std::move(other.data_);\n            return *this;\n        }\n#endif  // gsl_HAVE( MOVE_FORWARD )\n\n#if gsl_HAVE(IS_DEFAULT)\n        gsl_constexpr14 not_null(not_null const &) = default;\n        gsl_constexpr14 not_null &operator=(not_null const &) = default;\n#endif\n\n        gsl_api gsl_constexpr20 friend void swap(not_null &lhs, not_null &rhs)\n                gsl_noexcept_not_testing  // we want to be nothrow-swappable despite the\n                                          // precondition check\n        {\n            gsl_Expects(lhs.data_.ptr_ != gsl_nullptr && rhs.data_.ptr_ != gsl_nullptr);\n            using std::swap;\n            swap(lhs.data_.ptr_, rhs.data_.ptr_);\n        }\n\n        gsl_is_delete_access : not_null() gsl_is_delete;\n        // prevent compilation when initialized with a nullptr or literal 0:\n#if gsl_HAVE(NULLPTR)\n        not_null(std::nullptr_t) gsl_is_delete;\n        not_null &operator=(std::nullptr_t) gsl_is_delete;\n#else\n        not_null(int) gsl_is_delete;\n        not_null &operator=(int) gsl_is_delete;\n#endif\n\n        // unwanted operators...pointers only point to single objects!\n        not_null &operator++() gsl_is_delete;\n        not_null &operator--() gsl_is_delete;\n        not_null operator++(int) gsl_is_delete;\n        not_null operator--(int) gsl_is_delete;\n        not_null &operator+(size_t) gsl_is_delete;\n        not_null &operator+=(size_t) gsl_is_delete;\n        not_null &operator-(size_t) gsl_is_delete;\n        not_null &operator-=(size_t) gsl_is_delete;\n        not_null &operator+=(std::ptrdiff_t) gsl_is_delete;\n        not_null &operator-=(std::ptrdiff_t) gsl_is_delete;\n        void operator[](std::ptrdiff_t) const gsl_is_delete;\n    };\n#if gsl_CONFIG_DEFAULTS_VERSION >= 1\n    template <class T>\n    class not_null<T *> {\n    private:\n        detail::not_null_data<T *, true> data_;\n\n        // need to access `not_null<U>::data_`\n        template <class U>\n        friend class not_null;\n\n        template <class U>\n        friend struct detail::not_null_accessor;\n\n    public:\n        typedef T element_type;\n\n        gsl_api gsl_constexpr14\n#if gsl_CONFIG(NOT_NULL_EXPLICIT_CTOR)\n                explicit\n#endif  // gsl_CONFIG( NOT_NULL_EXPLICIT_CTOR )\n                not_null(T *other)\n                : data_(other) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n\n#if gsl_HAVE(MOVE_FORWARD)\n        // In Clang 3.x, `is_constructible<not_null<unique_ptr<X>>, unique_ptr<X>>`\n        // tries to instantiate the copy constructor of `unique_ptr<>`, triggering\n        // an error. Note that Apple Clang's `__clang_major__` etc. are different\n        // from regular Clang.\n#if gsl_HAVE(TYPE_TRAITS) && gsl_HAVE(DEFAULT_FUNCTION_TEMPLATE_ARG) && \\\n        !gsl_BETWEEN(gsl_COMPILER_CLANG_VERSION, 1, 400) &&             \\\n        !gsl_BETWEEN(gsl_COMPILER_APPLECLANG_VERSION, 1, 1001)\n        template <class U\n                  // We *have* to use SFINAE with an NTTP arg here, otherwise the\n                  // overload is ambiguous.\n                  ,\n                  typename std::enable_if<(std::is_constructible<T *, U>::value &&\n                                           !std::is_convertible<U, T *>::value),\n                                          int>::type = 0>\n        gsl_api gsl_constexpr14 explicit not_null(not_null<U> other)\n                : data_(static_cast<T *>(std::move(other.data_.ptr_))) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n\n        template <class U\n                  // We *have* to use SFINAE with an NTTP arg here, otherwise the\n                  // overload is ambiguous.\n                  ,\n                  typename std::enable_if<(std::is_convertible<U, T *>::value), int>::type = 0>\n        gsl_api gsl_constexpr14 not_null(not_null<U> other) : data_(std::move(other.data_.ptr_)) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n#else   // a.k.a. ! ( gsl_HAVE( TYPE_TRAITS ) && gsl_HAVE(        \\\n        // DEFAULT_FUNCTION_TEMPLATE_ARG ) && ! gsl_BETWEEN(      \\\n        // gsl_COMPILER_CLANG_VERSION, 1, 400 ) && ! gsl_BETWEEN( \\\n        // gsl_COMPILER_APPLECLANG_VERSION, 1, 1001 )                  \\\n        // If type_traits are not available, then we can't distinguish               \\\n        // `is_convertible<>` and `is_constructible<>`, so we unconditionally permit \\\n        // implicit construction.\n        template <class U>\n        gsl_api gsl_constexpr14 not_null(not_null<U> other) : data_(std::move(other.data_.ptr_)) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n        template <class U>\n        gsl_api gsl_constexpr14 not_null<T *> &operator=(not_null<U> other) {\n            gsl_Expects(other.data_.ptr_ != gsl_nullptr);\n            data_.ptr_ = std::move(other.data_.ptr_);\n            return *this;\n        }\n#endif  // gsl_HAVE( TYPE_TRAITS ) && gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) \\\n        // && ! gsl_BETWEEN( gsl_COMPILER_CLANG_VERSION, 1, 400 ) && !          \\\n        // gsl_BETWEEN( gsl_COMPILER_APPLECLANG_VERSION, 1, 1001 )\n#else   // a.k.a. ! gsl_HAVE( MOVE_FORWARD )\n        template <class U>\n        gsl_api gsl_constexpr14 not_null(not_null<U> const &other) : data_(other.data_.ptr_) {\n            gsl_Expects(data_.ptr_ != gsl_nullptr);\n        }\n        template <class U>\n        gsl_api gsl_constexpr14 not_null<T *> &operator=(not_null<U> const &other) {\n            gsl_Expects(other.data_.ptr_ != gsl_nullptr);\n            data_.ptr_ = other.data_.ptr_;\n            return *this;\n        }\n#endif  // gsl_HAVE( MOVE_FORWARD )\n\n#if !gsl_CONFIG(TRANSPARENT_NOT_NULL)\n        gsl_NODISCARD gsl_api gsl_constexpr14 T *get() const { return data_.ptr_; }\n#endif\n\n#if gsl_HAVE(TYPE_TRAITS) && gsl_HAVE(DEFAULT_FUNCTION_TEMPLATE_ARG) && gsl_HAVE(EXPLICIT)\n        // explicit conversion operator\n        template <class U\n                  // We *have* to use SFINAE with an NTTP arg here, otherwise the overload\n                  // is ambiguous.\n                  ,\n                  typename std::enable_if<(std::is_constructible<U, T *>::value &&\n                                           !std::is_convertible<T *, U>::value &&\n                                           !detail::is_not_null_or_bool_oracle<U>::value),\n                                          int>::type = 0>\n        gsl_NODISCARD gsl_api gsl_constexpr14 explicit operator U() const {\n            return U(data_.ptr_);\n        }\n\n        // implicit conversion operator\n        template <class U\n                  // We *have* to use SFINAE with an NTTP arg here, otherwise the overload\n                  // is ambiguous.\n                  ,\n                  typename std::enable_if<(std::is_constructible<U, T *>::value &&\n                                           std::is_convertible<T *, U>::value &&\n                                           !detail::is_not_null_or_bool_oracle<U>::value),\n                                          int>::type = 0>\n        gsl_NODISCARD gsl_api gsl_constexpr14 operator U() const {\n            return data_.ptr_;\n        }\n#else   // a.k.a. #if !( gsl_HAVE( MOVE_FORWARD ) && gsl_HAVE( TYPE_TRAITS ) && \\\n        // gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) && gsl_HAVE( EXPLICIT ) )\n        template <class U>\n        gsl_NODISCARD gsl_api gsl_constexpr14 operator U() const {\n            return data_.ptr_;\n        }\n#endif  // gsl_HAVE( MOVE_FORWARD ) && gsl_HAVE( TYPE_TRAITS ) && gsl_HAVE( \\\n        // DEFAULT_FUNCTION_TEMPLATE_ARG ) && gsl_HAVE( EXPLICIT )\n\n        gsl_NODISCARD gsl_api gsl_constexpr14 T *operator->() const { return data_.ptr_; }\n\n        gsl_NODISCARD gsl_api gsl_constexpr14 element_type &operator*() const {\n            return *data_.ptr_;\n        }\n\n#if gsl_HAVE(IS_DEFAULT)\n        gsl_constexpr14 not_null(not_null const &) = default;\n        gsl_constexpr14 not_null &operator=(not_null const &) = default;\n#endif\n\n        gsl_api gsl_constexpr20 friend void swap(not_null &lhs, not_null &rhs) gsl_noexcept {\n            using std::swap;\n            swap(lhs.data_.ptr_, rhs.data_.ptr_);\n        }\n\n        gsl_is_delete_access : not_null() gsl_is_delete;\n        // prevent compilation when initialized with a nullptr or literal 0:\n#if gsl_HAVE(NULLPTR)\n        not_null(std::nullptr_t) gsl_is_delete;\n        not_null &operator=(std::nullptr_t) gsl_is_delete;\n#else\n        not_null(int) gsl_is_delete;\n        not_null &operator=(int) gsl_is_delete;\n#endif\n\n        // unwanted operators...pointers only point to single objects!\n        not_null &operator++() gsl_is_delete;\n        not_null &operator--() gsl_is_delete;\n        not_null operator++(int) gsl_is_delete;\n        not_null operator--(int) gsl_is_delete;\n        not_null &operator+(size_t) gsl_is_delete;\n        not_null &operator+=(size_t) gsl_is_delete;\n        not_null &operator-(size_t) gsl_is_delete;\n        not_null &operator-=(size_t) gsl_is_delete;\n        not_null &operator+=(std::ptrdiff_t) gsl_is_delete;\n        not_null &operator-=(std::ptrdiff_t) gsl_is_delete;\n        void operator[](std::ptrdiff_t) const gsl_is_delete;\n    };\n#endif  // gsl_CONFIG_DEFAULTS_VERSION >= 1\n#if gsl_HAVE(DEDUCTION_GUIDES)\n    template <class U>\n    not_null(U) -> not_null<U>;\n    template <class U>\n    not_null(not_null<U>) -> not_null<U>;\n#endif\n\n#if gsl_HAVE(NULLPTR)\n    void make_not_null(std::nullptr_t) gsl_is_delete;\n#endif  // gsl_HAVE( NULLPTR )\n#if gsl_HAVE(MOVE_FORWARD)\n    template <class U>\n    gsl_NODISCARD gsl_api gsl_constexpr14 not_null<U> make_not_null(U u) {\n        return not_null<U>(std::move(u));\n    }\n    template <class U>\n    gsl_NODISCARD gsl_api gsl_constexpr14 not_null<U> make_not_null(not_null<U> u) {\n        return std::move(u);\n    }\n#else   // a.k.a. ! gsl_HAVE( MOVE_FORWARD )\n    template <class U>\n    gsl_NODISCARD gsl_api not_null<U> make_not_null(U const &u) {\n        return not_null<U>(u);\n    }\n    template <class U>\n    gsl_NODISCARD gsl_api not_null<U> make_not_null(not_null<U> const &u) {\n        return u;\n    }\n#endif  // gsl_HAVE( MOVE_FORWARD )\n\n    namespace detail {\n\n    template <class T>\n    struct as_nullable_helper {\n        typedef T type;\n    };\n    template <class T>\n    struct as_nullable_helper<not_null<T>> {};\n\n    template <class T>\n    struct not_null_accessor {\n#if gsl_HAVE(MOVE_FORWARD)\n        static gsl_api T get(not_null<T> &&p) gsl_noexcept { return std::move(p.data_.ptr_); }\n#endif\n        static gsl_api T const &get(not_null<T> const &p) gsl_noexcept { return p.data_.ptr_; }\n    };\n\n    namespace no_adl {\n\n#if gsl_HAVE(MOVE_FORWARD)\n    template <class T>\n    gsl_NODISCARD gsl_api gsl_constexpr auto as_nullable(T &&p)\n            gsl_noexcept_if(std::is_nothrow_move_constructible<T>::value) ->\n            typename detail::as_nullable_helper<typename std20::remove_cvref<T>::type>::type {\n        return std::move(p);\n    }\n    template <class T>\n    gsl_NODISCARD gsl_api gsl_constexpr14 T as_nullable(not_null<T> &&p) {\n        T result = detail::not_null_accessor<T>::get(std::move(p));\n        gsl_Expects(result != gsl_nullptr);\n        return result;\n    }\n#else   // ! gsl_HAVE( MOVE_FORWARD )\n    template <class T>\n    gsl_NODISCARD gsl_api gsl_constexpr T const &as_nullable(T const &p) gsl_noexcept {\n        return p;\n    }\n#endif  // gsl_HAVE( MOVE_FORWARD )\n    template <class T>\n    gsl_NODISCARD gsl_api gsl_constexpr14 T const &as_nullable(not_null<T> const &p) {\n        T const &result = detail::not_null_accessor<T>::get(p);\n        gsl_Expects(result != gsl_nullptr);\n        return result;\n    }\n    template <class T>\n    gsl_NODISCARD gsl_api gsl_constexpr T *as_nullable(not_null<T *> p) gsl_noexcept {\n        return detail::not_null_accessor<T *>::get(p);\n    }\n\n    }  // namespace no_adl\n    }  // namespace detail\n\n    using namespace detail::no_adl;\n\n    // not_null with implicit constructor, allowing copy-initialization:\n\n    template <class T>\n    class not_null_ic : public not_null<T> {\n    public:\n        template <class U gsl_ENABLE_IF_((std::is_constructible<T, U>::value))>\n        gsl_api gsl_constexpr14\n#if gsl_HAVE(MOVE_FORWARD)\n        not_null_ic(U &&u)\n                : not_null<T>(std::forward<U>(u))\n#else   // ! gsl_HAVE( MOVE_FORWARD )\n        not_null_ic(U const &u)\n                : not_null<T>(u)\n#endif  // gsl_HAVE( MOVE_FORWARD )\n        {\n        }\n    };\n\n    // more not_null unwanted operators\n\n    template <class T, class U>\n    std::ptrdiff_t operator-(not_null<T> const &, not_null<U> const &) gsl_is_delete;\n\n    template <class T>\n    not_null<T> operator-(not_null<T> const &, std::ptrdiff_t) gsl_is_delete;\n\n    template <class T>\n    not_null<T> operator+(not_null<T> const &, std::ptrdiff_t) gsl_is_delete;\n\n    template <class T>\n    not_null<T> operator+(std::ptrdiff_t, not_null<T> const &) gsl_is_delete;\n\n    // not_null comparisons\n\n#if gsl_HAVE(NULLPTR) && gsl_HAVE(IS_DELETE)\n    template <class T>\n    gsl_constexpr bool operator==(not_null<T> const &, std::nullptr_t) = delete;\n    template <class T>\n    gsl_constexpr bool operator==(std::nullptr_t, not_null<T> const &) = delete;\n    template <class T>\n    gsl_constexpr bool operator!=(not_null<T> const &, std::nullptr_t) = delete;\n    template <class T>\n    gsl_constexpr bool operator!=(std::nullptr_t, not_null<T> const &) = delete;\n#endif  // gsl_HAVE( NULLPTR ) && gsl_HAVE( IS_DELETE )\n\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator==(\n            not_null<T> const &l, not_null<U> const &r)\n            gsl_RETURN_DECLTYPE_(l.operator->() == r.operator->()) {\n        return l.operator->() == r.operator->();\n    }\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator==(\n            not_null<T> const &l, U const &r) gsl_RETURN_DECLTYPE_(l.operator->() == r) {\n        return l.operator->() == r;\n    }\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator==(\n            T const &l, not_null<U> const &r) gsl_RETURN_DECLTYPE_(l == r.operator->()) {\n        return l == r.operator->();\n    }\n\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator<(\n            not_null<T> const &l, not_null<U> const &r)\n            gsl_RETURN_DECLTYPE_(l.operator->() < r.operator->()) {\n        return l.operator->() < r.operator->();\n    }\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator<(\n            not_null<T> const &l, U const &r) gsl_RETURN_DECLTYPE_(l.operator->() < r) {\n        return l.operator->() < r;\n    }\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator<(\n            T const &l, not_null<U> const &r) gsl_RETURN_DECLTYPE_(l < r.operator->()) {\n        return l < r.operator->();\n    }\n\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator!=(\n            not_null<T> const &l, not_null<U> const &r) gsl_RETURN_DECLTYPE_(!(l == r)) {\n        return !(l == r);\n    }\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator!=(\n            not_null<T> const &l, U const &r) gsl_RETURN_DECLTYPE_(!(l == r)) {\n        return !(l == r);\n    }\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator!=(\n            T const &l, not_null<U> const &r) gsl_RETURN_DECLTYPE_(!(l == r)) {\n        return !(l == r);\n    }\n\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator<=(\n            not_null<T> const &l, not_null<U> const &r) gsl_RETURN_DECLTYPE_(!(r < l)) {\n        return !(r < l);\n    }\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator<=(\n            not_null<T> const &l, U const &r) gsl_RETURN_DECLTYPE_(!(r < l)) {\n        return !(r < l);\n    }\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator<=(\n            T const &l, not_null<U> const &r) gsl_RETURN_DECLTYPE_(!(r < l)) {\n        return !(r < l);\n    }\n\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator>(\n            not_null<T> const &l, not_null<U> const &r) gsl_RETURN_DECLTYPE_(r < l) {\n        return r < l;\n    }\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator>(\n            not_null<T> const &l, U const &r) gsl_RETURN_DECLTYPE_(r < l) {\n        return r < l;\n    }\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator>(\n            T const &l, not_null<U> const &r) gsl_RETURN_DECLTYPE_(r < l) {\n        return r < l;\n    }\n\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator>=(\n            not_null<T> const &l, not_null<U> const &r) gsl_RETURN_DECLTYPE_(!(l < r)) {\n        return !(l < r);\n    }\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator>=(\n            not_null<T> const &l, U const &r) gsl_RETURN_DECLTYPE_(!(l < r)) {\n        return !(l < r);\n    }\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_api gsl_constexpr gsl_TRAILING_RETURN_TYPE_(bool) operator>=(\n            T const &l, not_null<U> const &r) gsl_RETURN_DECLTYPE_(!(l < r)) {\n        return !(l < r);\n    }\n\n    // print not_null\n\n    template <class CharType, class Traits, class T>\n    std::basic_ostream<CharType, Traits> &operator<<(std::basic_ostream<CharType, Traits> &os,\n                                                     not_null<T> const &p) {\n        return os << p.operator->();\n    }\n\n//\n// Byte-specific type.\n//\n#if gsl_HAVE(ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE)\n    enum class gsl_may_alias byte : unsigned char {};\n#else\n    struct gsl_may_alias byte {\n        typedef unsigned char type;\n        type v;\n    };\n#endif\n\n    template <class T>\n    gsl_NODISCARD gsl_api inline gsl_constexpr byte to_byte(T v) gsl_noexcept {\n#if gsl_HAVE(ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE)\n        return static_cast<byte>(v);\n#elif gsl_HAVE(CONSTEXPR_11)\n        return {static_cast<typename byte::type>(v)};\n#else\n        byte b = {static_cast<typename byte::type>(v)};\n        return b;\n#endif\n    }\n\n    template <class IntegerType gsl_ENABLE_IF_((std::is_integral<IntegerType>::value))>\n    gsl_NODISCARD gsl_api inline gsl_constexpr IntegerType to_integer(byte b) gsl_noexcept {\n#if gsl_HAVE(ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE)\n        return static_cast<typename std::underlying_type<byte>::type>(b);\n#else\n        return b.v;\n#endif\n    }\n\n    gsl_NODISCARD gsl_api inline gsl_constexpr unsigned char to_uchar(byte b) gsl_noexcept {\n        return to_integer<unsigned char>(b);\n    }\n\n    gsl_NODISCARD gsl_api inline gsl_constexpr unsigned char to_uchar(int i) gsl_noexcept {\n        return static_cast<unsigned char>(i);\n    }\n\n    template <class IntegerType gsl_ENABLE_IF_((std::is_integral<IntegerType>::value))>\n    gsl_api inline gsl_constexpr14 byte &operator<<=(byte &b, IntegerType shift) gsl_noexcept {\n#if gsl_HAVE(ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE)\n        return b = ::gsl::to_byte(::gsl::to_uchar(b) << shift);\n#else\n        b.v = ::gsl::to_uchar(b.v << shift);\n        return b;\n#endif\n    }\n\n    template <class IntegerType gsl_ENABLE_IF_((std::is_integral<IntegerType>::value))>\n    gsl_NODISCARD gsl_api inline gsl_constexpr byte operator<<(byte b, IntegerType shift)\n            gsl_noexcept {\n        return ::gsl::to_byte(::gsl::to_uchar(b) << shift);\n    }\n\n    template <class IntegerType gsl_ENABLE_IF_((std::is_integral<IntegerType>::value))>\n    gsl_NODISCARD gsl_api inline gsl_constexpr14 byte &operator>>=(byte &b, IntegerType shift)\n            gsl_noexcept {\n#if gsl_HAVE(ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE)\n        return b = ::gsl::to_byte(::gsl::to_uchar(b) >> shift);\n#else\n        b.v = ::gsl::to_uchar(b.v >> shift);\n        return b;\n#endif\n    }\n\n    template <class IntegerType gsl_ENABLE_IF_((std::is_integral<IntegerType>::value))>\n    gsl_NODISCARD gsl_api inline gsl_constexpr byte operator>>(byte b, IntegerType shift)\n            gsl_noexcept {\n        return ::gsl::to_byte(::gsl::to_uchar(b) >> shift);\n    }\n\n#if gsl_HAVE(ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE)\n    gsl_DEFINE_ENUM_BITMASK_OPERATORS(byte) gsl_DEFINE_ENUM_RELATIONAL_OPERATORS(byte)\n#else   // a.k.a. !gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE )\n    gsl_api inline gsl_constexpr bool operator==(byte l, byte r) gsl_noexcept { return l.v == r.v; }\n\n    gsl_api inline gsl_constexpr bool operator!=(byte l, byte r) gsl_noexcept { return !(l == r); }\n\n    gsl_api inline gsl_constexpr bool operator<(byte l, byte r) gsl_noexcept { return l.v < r.v; }\n\n    gsl_api inline gsl_constexpr bool operator<=(byte l, byte r) gsl_noexcept { return !(r < l); }\n\n    gsl_api inline gsl_constexpr bool operator>(byte l, byte r) gsl_noexcept { return (r < l); }\n\n    gsl_api inline gsl_constexpr bool operator>=(byte l, byte r) gsl_noexcept { return !(l < r); }\n\n    gsl_api inline gsl_constexpr14 byte &operator|=(byte &l, byte r) gsl_noexcept {\n        l.v |= r.v;\n        return l;\n    }\n\n    gsl_api inline gsl_constexpr byte operator|(byte l, byte r) gsl_noexcept {\n        return ::gsl::to_byte(l.v | r.v);\n    }\n\n    gsl_api inline gsl_constexpr14 byte &operator&=(byte &l, byte r) gsl_noexcept {\n        l.v &= r.v;\n        return l;\n    }\n\n    gsl_api inline gsl_constexpr byte operator&(byte l, byte r) gsl_noexcept {\n        return ::gsl::to_byte(l.v & r.v);\n    }\n\n    gsl_api inline gsl_constexpr14 byte &operator^=(byte &l, byte r) gsl_noexcept {\n        l.v ^= r.v;\n        return l;\n    }\n\n    gsl_api inline gsl_constexpr byte operator^(byte l, byte r) gsl_noexcept {\n        return ::gsl::to_byte(l.v ^ r.v);\n    }\n\n    gsl_api inline gsl_constexpr byte operator~(byte b) gsl_noexcept {\n        return ::gsl::to_byte(~b.v);\n    }\n#endif  // gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE )\n\n#if gsl_FEATURE_TO_STD(WITH_CONTAINER)\n\n            // Tag to select span constructor taking a container:\n\n            struct with_container_t {\n        gsl_constexpr with_container_t() gsl_noexcept {}\n    };\n    const gsl_constexpr with_container_t\n            with_container;  // TODO: this can lead to ODR violations because the\n                             // symbol will be defined in multiple translation units\n\n#endif\n\n    namespace detail {\n\n    template <class T>\n    gsl_api gsl_constexpr14 T *endptr(T *data, gsl_CONFIG_SPAN_INDEX_TYPE size) {\n        // Be sure to run the check before doing pointer arithmetics, which would be\n        // UB for `nullptr` and non-0 integers.\n        gsl_Expects(size == 0 || data != gsl_nullptr);\n        return data + size;\n    }\n\n    }  // namespace detail\n\n    //\n    // span<> - A 1D view of contiguous T's, replace (*,len).\n    //\n    template <class T>\n    class span {\n        template <class U>\n        friend class span;\n\n    public:\n        typedef gsl_CONFIG_SPAN_INDEX_TYPE index_type;\n\n        typedef T element_type;\n        typedef typename std11::remove_cv<T>::type value_type;\n\n        typedef T &reference;\n        typedef T *pointer;\n        typedef T const *const_pointer;\n        typedef T const &const_reference;\n\n        typedef pointer iterator;\n        typedef const_pointer const_iterator;\n\n        typedef std::reverse_iterator<iterator> reverse_iterator;\n        typedef std::reverse_iterator<const_iterator> const_reverse_iterator;\n\n        typedef gsl_CONFIG_SPAN_INDEX_TYPE size_type;\n        typedef std::ptrdiff_t difference_type;\n\n        // 26.7.3.2 Constructors, copy, and assignment [span.cons]\n\n        gsl_api gsl_constexpr span() gsl_noexcept : first_(gsl_nullptr), last_(gsl_nullptr) {}\n\n#if !gsl_DEPRECATE_TO_LEVEL(5)\n\n#if gsl_HAVE(NULLPTR)\n        gsl_api gsl_constexpr14 span(std::nullptr_t, index_type size_in)\n                : first_(nullptr), last_(nullptr) {\n            gsl_Expects(size_in == 0);\n        }\n#endif\n\n#if gsl_HAVE(IS_DELETE)\n        gsl_DEPRECATED gsl_api gsl_constexpr span(reference data_in) : span(&data_in, 1) {}\n\n        gsl_api gsl_constexpr span(element_type &&) = delete;\n#endif\n\n#endif  // deprecate\n\n        gsl_api gsl_constexpr14 span(pointer data_in, index_type size_in)\n                : first_(data_in), last_(detail::endptr(data_in, size_in)) {}\n\n        gsl_api gsl_constexpr14 span(pointer first_in, pointer last_in)\n                : first_(first_in), last_(last_in) {\n            gsl_Expects(first_in <= last_in);\n        }\n\n#if !gsl_DEPRECATE_TO_LEVEL(5)\n\n        template <class U>\n        gsl_api gsl_constexpr14 span(U *data_in, index_type size_in)\n                : first_(data_in), last_(detail::endptr(data_in, size_in)) {}\n\n#endif  // deprecate\n\n#if !gsl_DEPRECATE_TO_LEVEL(5)\n        template <class U, size_t N>\n        gsl_api gsl_constexpr span(U (&arr)[N]) gsl_noexcept : first_(gsl_ADDRESSOF(arr[0])),\n                                                               last_(gsl_ADDRESSOF(arr[0]) + N) {}\n#else\n        template <size_t N gsl_ENABLE_IF_(\n                (std::is_convertible<value_type (*)[], element_type (*)[]>::value))>\n        gsl_api gsl_constexpr span(element_type (&arr)[N]) gsl_noexcept\n                : first_(gsl_ADDRESSOF(arr[0])),\n                  last_(gsl_ADDRESSOF(arr[0]) + N) {}\n#endif  // deprecate\n\n#if gsl_HAVE(ARRAY)\n#if !gsl_DEPRECATE_TO_LEVEL(5)\n\n        template <class U, size_t N>\n        gsl_api gsl_constexpr span(std::array<U, N> &arr)\n                : first_(arr.data()), last_(arr.data() + N) {}\n\n        template <class U, size_t N>\n        gsl_api gsl_constexpr span(std::array<U, N> const &arr)\n                : first_(arr.data()), last_(arr.data() + N) {}\n\n#else\n\n        template <size_t N gsl_ENABLE_IF_(\n                (std::is_convertible<value_type (*)[], element_type (*)[]>::value))>\n        gsl_constexpr span(std::array<value_type, N> &arr)\n                : first_(arr.data()), last_(arr.data() + N) {}\n\n        template <size_t N gsl_ENABLE_IF_(\n                (std::is_convertible<value_type (*)[], element_type (*)[]>::value))>\n        gsl_constexpr span(std::array<value_type, N> const &arr)\n                : first_(arr.data()), last_(arr.data() + N) {}\n\n#endif  // deprecate\n#endif  // gsl_HAVE( ARRAY )\n\n#if gsl_HAVE(CONSTRAINED_SPAN_CONTAINER_CTOR)\n        template <class Container gsl_ENABLE_IF_(\n                (detail::is_compatible_container<Container, element_type>::value))>\n        gsl_api gsl_constexpr span(Container &cont) gsl_noexcept\n                : first_(std17::data(cont)),\n                  last_(std17::data(cont) + std17::size(cont)) {}\n\n        template <class Container gsl_ENABLE_IF_(\n                (std::is_const<element_type>::value &&\n                 detail::is_compatible_container<Container, element_type>::value))>\n        gsl_api gsl_constexpr span(Container const &cont) gsl_noexcept\n                : first_(std17::data(cont)),\n                  last_(std17::data(cont) + std17::size(cont)) {}\n\n#elif gsl_HAVE(UNCONSTRAINED_SPAN_CONTAINER_CTOR)\n\n        template <class Container>\n        gsl_constexpr span(Container &cont)\n                : first_(cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF(cont[0])),\n                  last_(cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF(cont[0]) + cont.size()) {}\n\n        template <class Container>\n        gsl_constexpr span(Container const &cont)\n                : first_(cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF(cont[0])),\n                  last_(cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF(cont[0]) + cont.size()) {}\n\n#endif\n\n#if gsl_FEATURE_TO_STD(WITH_CONTAINER)\n\n        template <class Container>\n        gsl_constexpr span(with_container_t, Container &cont) gsl_noexcept\n                : first_(cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF(cont[0])),\n                  last_(cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF(cont[0]) + cont.size()) {}\n\n        template <class Container>\n        gsl_constexpr span(with_container_t, Container const &cont) gsl_noexcept\n                : first_(cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF(cont[0])),\n                  last_(cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF(cont[0]) + cont.size()) {}\n\n#endif\n\n#if !gsl_DEPRECATE_TO_LEVEL(4)\n        // constructor taking shared_ptr deprecated since 0.29.0\n\n#if gsl_HAVE(SHARED_PTR)\n        gsl_DEPRECATED gsl_constexpr span(shared_ptr<element_type> const &ptr)\n                : first_(ptr.get()), last_(ptr.get() ? ptr.get() + 1 : gsl_nullptr) {}\n#endif\n\n        // constructors taking unique_ptr deprecated since 0.29.0\n\n#if gsl_HAVE(UNIQUE_PTR)\n#if gsl_HAVE(DEFAULT_FUNCTION_TEMPLATE_ARG)\n        template <class ArrayElementType = typename std::add_pointer<element_type>::type>\n#else\n        template <class ArrayElementType>\n#endif\n        gsl_DEPRECATED gsl_constexpr span(unique_ptr<ArrayElementType> const &ptr, index_type count)\n                : first_(ptr.get()), last_(ptr.get() + count) {\n        }\n\n        gsl_DEPRECATED gsl_constexpr span(unique_ptr<element_type> const &ptr)\n                : first_(ptr.get()), last_(ptr.get() ? ptr.get() + 1 : gsl_nullptr) {}\n#endif\n\n#endif  // deprecate shared_ptr, unique_ptr\n\n#if gsl_HAVE(IS_DEFAULT) && !gsl_BETWEEN(gsl_COMPILER_GNUC_VERSION, 430, 600)\n        gsl_constexpr span(span &&) gsl_noexcept = default;\n        gsl_constexpr span(span const &) = default;\n#else\n        gsl_api gsl_constexpr span(span const &other) : first_(other.begin()), last_(other.end()) {}\n#endif\n\n#if gsl_HAVE(IS_DEFAULT)\n        gsl_constexpr14 span &operator=(span &&) gsl_noexcept = default;\n        gsl_constexpr14 span &operator=(span const &) gsl_noexcept = default;\n#else\n        gsl_constexpr14 span &operator=(span other) gsl_noexcept {\n            first_ = other.first_;\n            last_ = other.last_;\n            return *this;\n        }\n#endif\n\n        template <class U gsl_ENABLE_IF_((std::is_convertible<U (*)[], element_type (*)[]>::value))>\n        gsl_api gsl_constexpr span(span<U> const &other)\n                : first_(other.begin()), last_(other.end()) {}\n\n#if 0\n    // Converting from other span ?\n    template< class U > operator=();\n#endif\n\n        // 26.7.3.3 Subviews [span.sub]\n\n        gsl_NODISCARD gsl_api gsl_constexpr14 span first(index_type count) const {\n            gsl_Expects(std::size_t(count) <= std::size_t(this->size()));\n            return span(this->data(), count);\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr14 span last(index_type count) const {\n            gsl_Expects(std::size_t(count) <= std::size_t(this->size()));\n            return span(this->data() + this->size() - count, count);\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr14 span subspan(index_type offset) const {\n            gsl_Expects(std::size_t(offset) <= std::size_t(this->size()));\n            return span(this->data() + offset, this->size() - offset);\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr14 span subspan(index_type offset,\n                                                           index_type count) const {\n            gsl_Expects(std::size_t(offset) <= std::size_t(this->size()) &&\n                        std::size_t(count) <= std::size_t(this->size() - offset));\n            return span(this->data() + offset, count);\n        }\n\n        // 26.7.3.4 Observers [span.obs]\n\n        gsl_NODISCARD gsl_api gsl_constexpr index_type size() const gsl_noexcept {\n            return narrow_cast<index_type>(last_ - first_);\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr std::ptrdiff_t ssize() const gsl_noexcept {\n            return narrow_cast<std::ptrdiff_t>(last_ - first_);\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr index_type size_bytes() const gsl_noexcept {\n            return size() * narrow_cast<index_type>(sizeof(element_type));\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr bool empty() const gsl_noexcept { return size() == 0; }\n\n        // 26.7.3.5 Element access [span.elem]\n\n        gsl_NODISCARD gsl_api gsl_constexpr14 reference operator[](index_type pos) const {\n            gsl_Expects(pos < size());\n            return first_[pos];\n        }\n\n#if !gsl_DEPRECATE_TO_LEVEL(6)\n        gsl_DEPRECATED_MSG(\"use subscript indexing instead\") gsl_api gsl_constexpr14 reference\n        operator()(index_type pos) const {\n            return (*this)[pos];\n        }\n\n        gsl_DEPRECATED_MSG(\"use subscript indexing instead\") gsl_api gsl_constexpr14 reference\n                at(index_type pos) const {\n            return (*this)[pos];\n        }\n#endif  // deprecate\n\n        gsl_NODISCARD gsl_api gsl_constexpr14 reference front() const {\n            gsl_Expects(first_ != last_);\n            return *first_;\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr14 reference back() const {\n            gsl_Expects(first_ != last_);\n            return *(last_ - 1);\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr pointer data() const gsl_noexcept { return first_; }\n\n        // 26.7.3.6 Iterator support [span.iterators]\n\n        gsl_NODISCARD gsl_api gsl_constexpr iterator begin() const gsl_noexcept {\n            return iterator(first_);\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr iterator end() const gsl_noexcept {\n            return iterator(last_);\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr const_iterator cbegin() const gsl_noexcept {\n#if gsl_CPP11_OR_GREATER\n            return {begin()};\n#else\n            return const_iterator(begin());\n#endif\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr const_iterator cend() const gsl_noexcept {\n#if gsl_CPP11_OR_GREATER\n            return {end()};\n#else\n            return const_iterator(end());\n#endif\n        }\n\n        gsl_NODISCARD gsl_constexpr17 reverse_iterator rbegin() const gsl_noexcept {\n            return reverse_iterator(end());\n        }\n\n        gsl_NODISCARD gsl_constexpr17 reverse_iterator rend() const gsl_noexcept {\n            return reverse_iterator(begin());\n        }\n\n        gsl_NODISCARD gsl_constexpr17 const_reverse_iterator crbegin() const gsl_noexcept {\n            return const_reverse_iterator(cend());\n        }\n\n        gsl_NODISCARD gsl_constexpr17 const_reverse_iterator crend() const gsl_noexcept {\n            return const_reverse_iterator(cbegin());\n        }\n\n        gsl_constexpr14 void swap(span &other) gsl_noexcept {\n            std::swap(first_, other.first_);\n            std::swap(last_, other.last_);\n        }\n\n#if !gsl_DEPRECATE_TO_LEVEL(3)\n        // member length() deprecated since 0.29.0\n\n        gsl_DEPRECATED_MSG(\"use size() instead\") gsl_api gsl_constexpr index_type\n                length() const gsl_noexcept {\n            return size();\n        }\n\n        // member length_bytes() deprecated since 0.29.0\n\n        gsl_DEPRECATED_MSG(\"use size_bytes() instead\") gsl_api gsl_constexpr index_type\n                length_bytes() const gsl_noexcept {\n            return size_bytes();\n        }\n#endif\n\n#if !gsl_DEPRECATE_TO_LEVEL(2)\n        // member as_bytes(), as_writeable_bytes deprecated since 0.17.0\n\n        gsl_DEPRECATED_MSG(\"use free function gsl::as_bytes() instead\")\n                gsl_api span<const byte> as_bytes() const gsl_noexcept {\n            return span<const byte>(reinterpret_cast<const byte *>(data()),\n                                    size_bytes());  // NOLINT\n        }\n\n        gsl_DEPRECATED_MSG(\"use free function gsl::as_writable_bytes() instead\")\n                gsl_api span<byte> as_writeable_bytes() const gsl_noexcept {\n            return span<byte>(reinterpret_cast<byte *>(data()),\n                              size_bytes());  // NOLINT\n        }\n\n#endif\n\n        template <class U>\n        gsl_NODISCARD gsl_api span<U> as_span() const {\n            gsl_Expects((this->size_bytes() % sizeof(U)) == 0);\n            return span<U>(reinterpret_cast<U *>(this->data()),\n                           this->size_bytes() / sizeof(U));  // NOLINT\n        }\n\n    private:\n        pointer first_;\n        pointer last_;\n    };\n\n    // class template argument deduction guides:\n\n#if gsl_HAVE(DEDUCTION_GUIDES)  // gsl_CPP17_OR_GREATER\n\n    template <class T, size_t N>\n    span(T(&)[N]) -> span<T /*, N*/>;\n\n    template <class T, size_t N>\n    span(std::array<T, N> &) -> span<T /*, N*/>;\n\n    template <class T, size_t N>\n    span(std::array<T, N> const &) -> span<const T /*, N*/>;\n\n    template <class Container>\n    span(Container &) -> span<typename Container::value_type>;\n\n    template <class Container>\n    span(Container const &) -> span<const typename Container::value_type>;\n\n#endif  // gsl_HAVE( DEDUCTION_GUIDES )\n\n    // 26.7.3.7 Comparison operators [span.comparison]\n\n#if gsl_CONFIG(ALLOWS_SPAN_COMPARISON)\n#if gsl_CONFIG(ALLOWS_NONSTRICT_SPAN_COMPARISON)\n\n    template <class T, class U>\n    gsl_SUPPRESS_MSGSL_WARNING(stl .1) gsl_NODISCARD inline gsl_constexpr bool operator==(\n            span<T> const &l, span<U> const &r) {\n        return l.size() == r.size() &&\n               (l.begin() == r.begin() || std98::equal(l.begin(), l.end(), r.begin()));\n    }\n\n    template <class T, class U>\n    gsl_SUPPRESS_MSGSL_WARNING(stl .1) gsl_NODISCARD inline gsl_constexpr bool operator<(\n            span<T> const &l, span<U> const &r) {\n        return std98::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end());\n    }\n\n#else   // a.k.a. !gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON )\n\n    template <class T>\n    gsl_SUPPRESS_MSGSL_WARNING(stl .1) gsl_NODISCARD inline gsl_constexpr bool operator==(\n            span<T> const &l, span<T> const &r) {\n        return l.size() == r.size() &&\n               (l.begin() == r.begin() || std98::equal(l.begin(), l.end(), r.begin()));\n    }\n\n    template <class T>\n    gsl_SUPPRESS_MSGSL_WARNING(stl .1) gsl_NODISCARD inline gsl_constexpr bool operator<(\n            span<T> const &l, span<T> const &r) {\n        return std98::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end());\n    }\n#endif  // gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON )\n\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_constexpr bool operator!=(span<T> const &l, span<U> const &r) {\n        return !(l == r);\n    }\n\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_constexpr bool operator<=(span<T> const &l, span<U> const &r) {\n        return !(r < l);\n    }\n\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_constexpr bool operator>(span<T> const &l, span<U> const &r) {\n        return (r < l);\n    }\n\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_constexpr bool operator>=(span<T> const &l, span<U> const &r) {\n        return !(l < r);\n    }\n#endif  // gsl_CONFIG( ALLOWS_SPAN_COMPARISON )\n\n    // span algorithms\n\n    template <class T>\n    gsl_NODISCARD gsl_api inline gsl_constexpr std::size_t size(span<T> const &spn) {\n        return static_cast<std::size_t>(spn.size());\n    }\n\n    template <class T>\n    gsl_NODISCARD gsl_api inline gsl_constexpr std::ptrdiff_t ssize(span<T> const &spn) {\n        return spn.ssize();\n    }\n\n    namespace detail {\n\n    template <class II, class N, class OI>\n    gsl_api gsl_constexpr14 inline OI copy_n(II first, N count, OI result) {\n        if (count > 0) {\n            *result++ = *first;\n            for (N i = 1; i < count; ++i) {\n                *result++ = *++first;\n            }\n        }\n        return result;\n    }\n    }  // namespace detail\n\n    template <class T, class U>\n    gsl_api gsl_constexpr14 inline void copy(span<T> src, span<U> dest) {\n#if gsl_CPP14_OR_GREATER  // gsl_HAVE( TYPE_TRAITS ) (circumvent Travis \\\n                          // clang 3.4)\n        static_assert(std::is_assignable<U &, T const &>::value,\n                      \"Cannot assign elements of source span to elements of \"\n                      \"destination span\");\n#endif\n        gsl_Expects(dest.size() >= src.size());\n        detail::copy_n(src.data(), src.size(), dest.data());\n    }\n\n    // span creator functions (see ctors)\n\n    template <class T>\n    gsl_NODISCARD gsl_api inline span<const byte> as_bytes(span<T> spn) gsl_noexcept {\n        return span<const byte>(reinterpret_cast<const byte *>(spn.data()),\n                                spn.size_bytes());  // NOLINT\n    }\n\n    template <class T>\n    gsl_NODISCARD gsl_api inline span<byte> as_writable_bytes(span<T> spn) gsl_noexcept {\n        return span<byte>(reinterpret_cast<byte *>(spn.data()),\n                          spn.size_bytes());  // NOLINT\n    }\n\n#if !gsl_DEPRECATE_TO_LEVEL(6)\n    template <class T>\n    gsl_DEPRECATED_MSG(\"use as_writable_bytes() (different spelling) instead\")\n            gsl_api inline span<byte>\n            as_writeable_bytes(span<T> spn) gsl_noexcept {\n        return span<byte>(reinterpret_cast<byte *>(spn.data()),\n                          spn.size_bytes());  // NOLINT\n    }\n#endif  // deprecate\n\n#if gsl_FEATURE_TO_STD(MAKE_SPAN)\n\n    template <class T>\n    gsl_NODISCARD gsl_api inline gsl_constexpr span<T> make_span(\n            T * ptr, typename span<T>::index_type count) {\n        return span<T>(ptr, count);\n    }\n\n    template <class T>\n    gsl_NODISCARD gsl_api inline gsl_constexpr span<T> make_span(T * first, T * last) {\n        return span<T>(first, last);\n    }\n\n    template <class T, size_t N>\n    gsl_NODISCARD inline gsl_constexpr span<T> make_span(T(&arr)[N]) {\n        return span<T>(gsl_ADDRESSOF(arr[0]), N);\n    }\n\n#if gsl_HAVE(ARRAY)\n\n    template <class T, size_t N>\n    gsl_NODISCARD inline gsl_constexpr span<T> make_span(std::array<T, N> & arr) {\n        return span<T>(arr);\n    }\n\n    template <class T, size_t N>\n    gsl_NODISCARD inline gsl_constexpr span<const T> make_span(std::array<T, N> const &arr) {\n        return span<const T>(arr);\n    }\n#endif\n\n#if gsl_HAVE(CONSTRAINED_SPAN_CONTAINER_CTOR) && gsl_HAVE(AUTO)\n\n    template <class Container, class EP = decltype(std17::data(std::declval<Container &>()))>\n    gsl_NODISCARD inline gsl_constexpr auto make_span(Container & cont)\n            ->span<typename std::remove_pointer<EP>::type> {\n        return span<typename std::remove_pointer<EP>::type>(cont);\n    }\n\n    template <class Container, class EP = decltype(std17::data(std::declval<Container &>()))>\n    gsl_NODISCARD inline gsl_constexpr auto make_span(Container const &cont)\n            ->span<const typename std::remove_pointer<EP>::type> {\n        return span<const typename std::remove_pointer<EP>::type>(cont);\n    }\n\n#else\n\n    template <class T>\n    inline span<T> make_span(std::vector<T> & cont) {\n        return span<T>(with_container, cont);\n    }\n\n    template <class T>\n    inline span<const T> make_span(std::vector<T> const &cont) {\n        return span<const T>(with_container, cont);\n    }\n#endif\n\n#if gsl_FEATURE_TO_STD(WITH_CONTAINER)\n\n    template <class Container>\n    gsl_NODISCARD inline gsl_constexpr span<typename Container::value_type> make_span(\n            with_container_t, Container & cont) gsl_noexcept {\n        return span<typename Container::value_type>(with_container, cont);\n    }\n\n    template <class Container>\n    gsl_NODISCARD inline gsl_constexpr span<const typename Container::value_type> make_span(\n            with_container_t, Container const &cont) gsl_noexcept {\n        return span<const typename Container::value_type>(with_container, cont);\n    }\n\n#endif  // gsl_FEATURE_TO_STD( WITH_CONTAINER )\n\n#if !gsl_DEPRECATE_TO_LEVEL(4)\n    template <class Ptr>\n    gsl_DEPRECATED inline span<typename Ptr::element_type> make_span(Ptr & ptr) {\n        return span<typename Ptr::element_type>(ptr);\n    }\n#endif  // !gsl_DEPRECATE_TO_LEVEL( 4 )\n\n    template <class Ptr>\n    gsl_DEPRECATED inline span<typename Ptr::element_type> make_span(\n            Ptr & ptr, typename span<typename Ptr::element_type>::index_type count) {\n        return span<typename Ptr::element_type>(ptr, count);\n    }\n\n#endif  // gsl_FEATURE_TO_STD( MAKE_SPAN )\n\n#if gsl_FEATURE_TO_STD(BYTE_SPAN)\n\n    template <class T>\n    gsl_NODISCARD gsl_api inline gsl_constexpr span<byte> byte_span(T & t) gsl_noexcept {\n        return span<byte>(reinterpret_cast<byte *>(&t), sizeof(T));\n    }\n\n    template <class T>\n    gsl_NODISCARD gsl_api inline gsl_constexpr span<const byte> byte_span(T const &t) gsl_noexcept {\n        return span<const byte>(reinterpret_cast<byte const *>(&t), sizeof(T));\n    }\n\n#endif  // gsl_FEATURE_TO_STD( BYTE_SPAN )\n\n    //\n    // basic_string_span:\n    //\n\n    template <class T>\n    class basic_string_span;\n\n    namespace detail {\n\n    template <class T>\n    struct is_basic_string_span_oracle : std11::false_type {};\n\n    template <class T>\n    struct is_basic_string_span_oracle<basic_string_span<T>> : std11::true_type {};\n\n    template <class T>\n    struct is_basic_string_span : is_basic_string_span_oracle<typename std11::remove_cv<T>::type> {\n    };\n\n    template <class T>\n    gsl_api inline gsl_constexpr14 std::size_t string_length(T *ptr, std::size_t max) {\n        if (ptr == gsl_nullptr || max <= 0)\n            return 0;\n\n        std::size_t len = 0;\n        while (len < max && ptr[len])  // NOLINT\n            ++len;\n\n        return len;\n    }\n\n    }  // namespace detail\n\n    //\n    // basic_string_span<> - A view of contiguous characters, replace (*,len).\n    //\n    template <class T>\n    class basic_string_span {\n    public:\n        typedef T element_type;\n        typedef span<T> span_type;\n\n        typedef typename span_type::size_type size_type;\n        typedef typename span_type::index_type index_type;\n        typedef typename span_type::difference_type difference_type;\n\n        typedef typename span_type::pointer pointer;\n        typedef typename span_type::reference reference;\n\n        typedef typename span_type::iterator iterator;\n        typedef typename span_type::const_iterator const_iterator;\n        typedef typename span_type::reverse_iterator reverse_iterator;\n        typedef typename span_type::const_reverse_iterator const_reverse_iterator;\n\n        // construction:\n\n#if gsl_HAVE(IS_DEFAULT)\n        gsl_constexpr basic_string_span() gsl_noexcept = default;\n#else\n        gsl_api gsl_constexpr basic_string_span() gsl_noexcept {}\n#endif\n\n#if gsl_HAVE(NULLPTR)\n        gsl_api gsl_constexpr basic_string_span(std::nullptr_t) gsl_noexcept\n                : span_(nullptr, static_cast<index_type>(0)) {}\n#endif\n\n#ifdef __CUDACC_RELAXED_CONSTEXPR__\n        gsl_api\n#endif  // __CUDACC_RELAXED_CONSTEXPR__\n                gsl_constexpr\n                basic_string_span(pointer ptr)\n                : span_(remove_z(ptr, (std::numeric_limits<index_type>::max)())) {\n        }\n\n        gsl_api gsl_constexpr basic_string_span(pointer ptr, index_type count)\n                : span_(ptr, count) {}\n\n        gsl_api gsl_constexpr basic_string_span(pointer firstElem, pointer lastElem)\n                : span_(firstElem, lastElem) {}\n\n        template <std::size_t N>\n        gsl_constexpr basic_string_span(element_type (&arr)[N])\n                : span_(remove_z(gsl_ADDRESSOF(arr[0]), N)) {}\n\n#if gsl_HAVE(ARRAY)\n\n        template <std::size_t N>\n        gsl_constexpr basic_string_span(\n                std::array<typename std11::remove_const<element_type>::type, N> &arr)\n                : span_(remove_z(arr)) {}\n\n        template <std::size_t N>\n        gsl_constexpr basic_string_span(\n                std::array<typename std11::remove_const<element_type>::type, N> const &arr)\n                : span_(remove_z(arr)) {}\n\n#endif\n\n#if gsl_HAVE(CONSTRAINED_SPAN_CONTAINER_CTOR)\n\n        // Exclude: array, [basic_string,] basic_string_span\n\n        template <class Container gsl_ENABLE_IF_(\n                (!detail::is_std_array<Container>::value &&\n                 !detail::is_basic_string_span<Container>::value &&\n                 std::is_convertible<typename Container::pointer, pointer>::value &&\n                 std::is_convertible<typename Container::pointer,\n                                     decltype(std::declval<Container>().data())>::value))>\n        gsl_constexpr basic_string_span(Container &cont) : span_((cont)) {}\n\n        // Exclude: array, [basic_string,] basic_string_span\n\n        template <class Container gsl_ENABLE_IF_(\n                (!detail::is_std_array<Container>::value &&\n                 !detail::is_basic_string_span<Container>::value &&\n                 std::is_convertible<typename Container::pointer, pointer>::value &&\n                 std::is_convertible<typename Container::pointer,\n                                     decltype(std::declval<Container const &>().data())>::value))>\n        gsl_constexpr basic_string_span(Container const &cont) : span_((cont)) {}\n\n#elif gsl_HAVE(UNCONSTRAINED_SPAN_CONTAINER_CTOR)\n\n        template <class Container>\n        gsl_constexpr basic_string_span(Container &cont) : span_(cont) {}\n\n        template <class Container>\n        gsl_constexpr basic_string_span(Container const &cont) : span_(cont) {}\n\n#else\n\n        template <class U>\n        gsl_api gsl_constexpr basic_string_span(span<U> const &rhs) : span_(rhs) {}\n\n#endif\n\n#if gsl_FEATURE_TO_STD(WITH_CONTAINER)\n\n        template <class Container>\n        gsl_constexpr basic_string_span(with_container_t, Container &cont)\n                : span_(with_container, cont) {}\n#endif\n\n#if gsl_HAVE(IS_DEFAULT)\n#if gsl_BETWEEN(gsl_COMPILER_GNUC_VERSION, 440, 600)\n        gsl_constexpr basic_string_span(basic_string_span const &) = default;\n\n        gsl_constexpr basic_string_span(basic_string_span &&) = default;\n#else\n        gsl_constexpr basic_string_span(basic_string_span const &) gsl_noexcept = default;\n\n        gsl_constexpr basic_string_span(basic_string_span &&) gsl_noexcept = default;\n#endif\n#endif\n\n        template <class U gsl_ENABLE_IF_(\n                (std::is_convertible<typename basic_string_span<U>::pointer, pointer>::value))>\n        gsl_api gsl_constexpr basic_string_span(basic_string_span<U> const &rhs)\n                : span_(reinterpret_cast<pointer>(rhs.data()), rhs.length())  // NOLINT\n        {}\n\n#if gsl_STDLIB_CPP11_120\n        template <class U gsl_ENABLE_IF_(\n                (std::is_convertible<typename basic_string_span<U>::pointer, pointer>::value))>\n        gsl_api gsl_constexpr basic_string_span(basic_string_span<U> &&rhs)\n                : span_(reinterpret_cast<pointer>(rhs.data()), rhs.length())  // NOLINT\n        {}\n#endif  // gsl_STDLIB_CPP11_120\n\n        template <class CharTraits, class Allocator>\n        gsl_constexpr basic_string_span(\n                std::basic_string<typename std11::remove_const<element_type>::type,\n                                  CharTraits,\n                                  Allocator> &str)\n                : span_(gsl_ADDRESSOF(str[0]), str.length()) {}\n\n        template <class CharTraits, class Allocator>\n        gsl_constexpr basic_string_span(\n                std::basic_string<typename std11::remove_const<element_type>::type,\n                                  CharTraits,\n                                  Allocator> const &str)\n                : span_(gsl_ADDRESSOF(str[0]), str.length()) {}\n\n        // assignment:\n\n#if gsl_HAVE(IS_DEFAULT)\n        gsl_constexpr14 basic_string_span &operator=(basic_string_span const &)\n                gsl_noexcept = default;\n\n        gsl_constexpr14 basic_string_span &operator=(basic_string_span &&) gsl_noexcept = default;\n#endif\n\n        // sub span:\n\n        /*gsl_api*/  // currently disabled due to an apparent NVCC bug\n        gsl_NODISCARD gsl_constexpr14 basic_string_span first(index_type count) const {\n            return span_.first(count);\n        }\n\n        /*gsl_api*/  // currently disabled due to an apparent NVCC bug\n        gsl_NODISCARD gsl_constexpr14 basic_string_span last(index_type count) const {\n            return span_.last(count);\n        }\n\n        /*gsl_api*/  // currently disabled due to an apparent NVCC bug\n        gsl_NODISCARD gsl_constexpr14 basic_string_span subspan(index_type offset) const {\n            return span_.subspan(offset);\n        }\n\n        /*gsl_api*/  // currently disabled due to an apparent NVCC bug\n        gsl_NODISCARD gsl_constexpr14 basic_string_span subspan(index_type offset,\n                                                                index_type count) const {\n            return span_.subspan(offset, count);\n        }\n\n        // observers:\n\n        gsl_NODISCARD gsl_api gsl_constexpr index_type length() const gsl_noexcept {\n            return span_.size();\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr index_type size() const gsl_noexcept {\n            return span_.size();\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr index_type length_bytes() const gsl_noexcept {\n            return span_.size_bytes();\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr index_type size_bytes() const gsl_noexcept {\n            return span_.size_bytes();\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr bool empty() const gsl_noexcept { return size() == 0; }\n\n        gsl_NODISCARD gsl_api gsl_constexpr14 reference operator[](index_type idx) const {\n            return span_[idx];\n        }\n\n#if !gsl_DEPRECATE_TO_LEVEL(6)\n        gsl_DEPRECATED_MSG(\"use subscript indexing instead\") gsl_api gsl_constexpr14 reference\n        operator()(index_type idx) const {\n            return span_[idx];\n        }\n#endif  // deprecate\n\n        gsl_NODISCARD gsl_api gsl_constexpr14 reference front() const { return span_.front(); }\n\n        gsl_NODISCARD gsl_api gsl_constexpr14 reference back() const { return span_.back(); }\n\n        gsl_NODISCARD gsl_api gsl_constexpr pointer data() const gsl_noexcept {\n            return span_.data();\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr iterator begin() const gsl_noexcept {\n            return span_.begin();\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr iterator end() const gsl_noexcept {\n            return span_.end();\n        }\n\n        gsl_NODISCARD gsl_constexpr17 reverse_iterator rbegin() const gsl_noexcept {\n            return span_.rbegin();\n        }\n\n        gsl_NODISCARD gsl_constexpr17 reverse_iterator rend() const gsl_noexcept {\n            return span_.rend();\n        }\n\n        // const version not in p0123r2:\n\n        gsl_NODISCARD gsl_api gsl_constexpr const_iterator cbegin() const gsl_noexcept {\n            return span_.cbegin();\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr const_iterator cend() const gsl_noexcept {\n            return span_.cend();\n        }\n\n        gsl_NODISCARD gsl_constexpr17 const_reverse_iterator crbegin() const gsl_noexcept {\n            return span_.crbegin();\n        }\n\n        gsl_NODISCARD gsl_constexpr17 const_reverse_iterator crend() const gsl_noexcept {\n            return span_.crend();\n        }\n\n    private:\n        gsl_api static gsl_constexpr14 span_type remove_z(pointer sz, std::size_t max) {\n            return span_type(sz, detail::string_length(sz, max));\n        }\n\n#if gsl_HAVE(ARRAY)\n        template <size_t N>\n        gsl_NODISCARD static gsl_constexpr14 span_type\n        remove_z(std::array<typename std11::remove_const<element_type>::type, N> &arr) {\n            return remove_z(gsl_ADDRESSOF(arr[0]), narrow_cast<std::size_t>(N));\n        }\n\n        template <size_t N>\n        gsl_NODISCARD static gsl_constexpr14 span_type\n        remove_z(std::array<typename std11::remove_const<element_type>::type, N> const &arr) {\n            return remove_z(gsl_ADDRESSOF(arr[0]), narrow_cast<std::size_t>(N));\n        }\n#endif\n\n    private:\n        span_type span_;\n    };\n\n    // basic_string_span comparison functions:\n\n#if gsl_CONFIG(ALLOWS_NONSTRICT_SPAN_COMPARISON)\n\n    template <class T, class U>\n    gsl_SUPPRESS_MSGSL_WARNING(stl .1) gsl_NODISCARD inline gsl_constexpr14 bool operator==(\n            basic_string_span<T> const &l, U const &u) gsl_noexcept {\n        const basic_string_span<typename std11::add_const<T>::type> r(u);\n\n        return l.size() == r.size() && std98::equal(l.begin(), l.end(), r.begin());\n    }\n\n    template <class T, class U>\n    gsl_SUPPRESS_MSGSL_WARNING(stl .1) gsl_NODISCARD inline gsl_constexpr14 bool operator<(\n            basic_string_span<T> const &l, U const &u) gsl_noexcept {\n        const basic_string_span<typename std11::add_const<T>::type> r(u);\n\n        return std98::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end());\n    }\n\n#if gsl_HAVE(DEFAULT_FUNCTION_TEMPLATE_ARG)\n\n    template <class T, class U gsl_ENABLE_IF_((!detail::is_basic_string_span<U>::value))>\n    gsl_SUPPRESS_MSGSL_WARNING(stl .1) gsl_NODISCARD inline gsl_constexpr14 bool operator==(\n            U const &u, basic_string_span<T> const &r) gsl_noexcept {\n        const basic_string_span<typename std11::add_const<T>::type> l(u);\n\n        return l.size() == r.size() && std98::equal(l.begin(), l.end(), r.begin());\n    }\n\n    template <class T, class U gsl_ENABLE_IF_((!detail::is_basic_string_span<U>::value))>\n    gsl_SUPPRESS_MSGSL_WARNING(stl .1) gsl_NODISCARD inline gsl_constexpr14 bool operator<(\n            U const &u, basic_string_span<T> const &r) gsl_noexcept {\n        const basic_string_span<typename std11::add_const<T>::type> l(u);\n\n        return std98::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end());\n    }\n#endif\n\n#else  // gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON )\n\n    template <class T>\n    gsl_SUPPRESS_MSGSL_WARNING(stl .1) gsl_NODISCARD inline gsl_constexpr14 bool operator==(\n            basic_string_span<T> const &l, basic_string_span<T> const &r) gsl_noexcept {\n        return l.size() == r.size() && std98::equal(l.begin(), l.end(), r.begin());\n    }\n\n    template <class T>\n    gsl_SUPPRESS_MSGSL_WARNING(stl .1) gsl_NODISCARD inline gsl_constexpr14 bool operator<(\n            basic_string_span<T> const &l, basic_string_span<T> const &r) gsl_noexcept {\n        return std98::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end());\n    }\n\n#endif  // gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON )\n\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_constexpr14 bool operator!=(basic_string_span<T> const &l, U const &r)\n            gsl_noexcept {\n        return !(l == r);\n    }\n\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_constexpr14 bool operator<=(basic_string_span<T> const &l, U const &r)\n            gsl_noexcept {\n#if gsl_HAVE(DEFAULT_FUNCTION_TEMPLATE_ARG) || !gsl_CONFIG(ALLOWS_NONSTRICT_SPAN_COMPARISON)\n        return !(r < l);\n#else\n        basic_string_span<typename std11::add_const<T>::type> rr(r);\n        return !(rr < l);\n#endif\n    }\n\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_constexpr14 bool operator>(basic_string_span<T> const &l, U const &r)\n            gsl_noexcept {\n#if gsl_HAVE(DEFAULT_FUNCTION_TEMPLATE_ARG) || !gsl_CONFIG(ALLOWS_NONSTRICT_SPAN_COMPARISON)\n        return (r < l);\n#else\n        basic_string_span<typename std11::add_const<T>::type> rr(r);\n        return (rr < l);\n#endif\n    }\n\n    template <class T, class U>\n    gsl_NODISCARD inline gsl_constexpr14 bool operator>=(basic_string_span<T> const &l, U const &r)\n            gsl_noexcept {\n        return !(l < r);\n    }\n\n#if gsl_HAVE(DEFAULT_FUNCTION_TEMPLATE_ARG)\n\n    template <class T, class U gsl_ENABLE_IF_((!detail::is_basic_string_span<U>::value))>\n    gsl_NODISCARD inline gsl_constexpr14 bool operator!=(U const &l, basic_string_span<T> const &r)\n            gsl_noexcept {\n        return !(l == r);\n    }\n\n    template <class T, class U gsl_ENABLE_IF_((!detail::is_basic_string_span<U>::value))>\n    gsl_NODISCARD inline gsl_constexpr14 bool operator<=(U const &l, basic_string_span<T> const &r)\n            gsl_noexcept {\n        return !(r < l);\n    }\n\n    template <class T, class U gsl_ENABLE_IF_((!detail::is_basic_string_span<U>::value))>\n    gsl_NODISCARD inline gsl_constexpr14 bool operator>(U const &l, basic_string_span<T> const &r)\n            gsl_noexcept {\n        return (r < l);\n    }\n\n    template <class T, class U gsl_ENABLE_IF_((!detail::is_basic_string_span<U>::value))>\n    gsl_NODISCARD inline gsl_constexpr14 bool operator>=(U const &l, basic_string_span<T> const &r)\n            gsl_noexcept {\n        return !(l < r);\n    }\n\n#endif  // gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG )\n\n    // convert basic_string_span to byte span:\n\n    template <class T>\n    gsl_NODISCARD gsl_api inline span<const byte> as_bytes(basic_string_span<T> spn) gsl_noexcept {\n        return span<const byte>(reinterpret_cast<const byte *>(spn.data()),\n                                spn.size_bytes());  // NOLINT\n    }\n\n    //\n    // String types:\n    //\n\n    typedef char *zstring;\n    typedef const char *czstring;\n\n#if gsl_HAVE(WCHAR)\n    typedef wchar_t *wzstring;\n    typedef const wchar_t *cwzstring;\n#endif\n\n    typedef basic_string_span<char> string_span;\n    typedef basic_string_span<char const> cstring_span;\n\n#if gsl_HAVE(WCHAR)\n    typedef basic_string_span<wchar_t> wstring_span;\n    typedef basic_string_span<wchar_t const> cwstring_span;\n#endif\n\n    // to_string() allow (explicit) conversions from string_span to string\n\n#if 0\n\ntemplate< class T >\ninline std::basic_string< typename std::remove_const<T>::type > to_string( basic_string_span<T> spn )\n{\n     std::string( spn.data(), spn.length() );\n}\n\n#else\n\n    gsl_NODISCARD inline std::string to_string(string_span const &spn) {\n        return std::string(spn.data(), static_cast<std::size_t>(spn.length()));\n    }\n\n    gsl_NODISCARD inline std::string to_string(cstring_span const &spn) {\n        return std::string(spn.data(), static_cast<std::size_t>(spn.length()));\n    }\n\n#if gsl_HAVE(WCHAR)\n\n    gsl_NODISCARD inline std::wstring to_string(wstring_span const &spn) {\n        return std::wstring(spn.data(), static_cast<std::size_t>(spn.length()));\n    }\n\n    gsl_NODISCARD inline std::wstring to_string(cwstring_span const &spn) {\n        return std::wstring(spn.data(), static_cast<std::size_t>(spn.length()));\n    }\n\n#endif  // gsl_HAVE( WCHAR )\n#endif  // to_string()\n\n    //\n    // Stream output for string_span types\n    //\n\n    namespace detail {\n\n    template <class Stream>\n    void write_padding(Stream &os, std::streamsize n) {\n        for (std::streamsize i = 0; i < n; ++i)\n            os.rdbuf()->sputc(os.fill());\n    }\n\n    template <class Stream, class Span>\n    Stream &write_to_stream(Stream &os, Span const &spn) {\n        typename Stream::sentry sentry(os);\n\n        if (!os)\n            return os;\n\n        const std::streamsize length = gsl::narrow_failfast<std::streamsize>(spn.length());\n\n        // Whether, and how, to pad\n        const bool pad = (length < os.width());\n        const bool left_pad =\n                pad && (os.flags() & std::ios_base::adjustfield) == std::ios_base::right;\n\n        if (left_pad)\n            detail::write_padding(os, os.width() - length);\n\n        // Write span characters\n        os.rdbuf()->sputn(spn.begin(), length);\n\n        if (pad && !left_pad)\n            detail::write_padding(os, os.width() - length);\n\n        // Reset output stream width\n        os.width(0);\n\n        return os;\n    }\n\n    }  // namespace detail\n\n    template <typename Traits>\n    std::basic_ostream<char, Traits> &operator<<(std::basic_ostream<char, Traits> &os,\n                                                 string_span const &spn) {\n        return detail::write_to_stream(os, spn);\n    }\n\n    template <typename Traits>\n    std::basic_ostream<char, Traits> &operator<<(std::basic_ostream<char, Traits> &os,\n                                                 cstring_span const &spn) {\n        return detail::write_to_stream(os, spn);\n    }\n\n#if gsl_HAVE(WCHAR)\n\n    template <typename Traits>\n    std::basic_ostream<wchar_t, Traits> &operator<<(std::basic_ostream<wchar_t, Traits> &os,\n                                                    wstring_span const &spn) {\n        return detail::write_to_stream(os, spn);\n    }\n\n    template <typename Traits>\n    std::basic_ostream<wchar_t, Traits> &operator<<(std::basic_ostream<wchar_t, Traits> &os,\n                                                    cwstring_span const &spn) {\n        return detail::write_to_stream(os, spn);\n    }\n\n#endif  // gsl_HAVE( WCHAR )\n\n    //\n    // ensure_sentinel()\n    //\n    // Provides a way to obtain a span from a contiguous sequence\n    // that ends with a (non-inclusive) sentinel value.\n    //\n    // Will fail-fast if sentinel cannot be found before max elements are\n    // examined.\n    //\n    namespace detail {\n\n    template <class T, class SizeType, const T Sentinel>\n    gsl_constexpr14 static span<T> ensure_sentinel(\n            T *seq,\n            SizeType max = (std::numeric_limits<SizeType>::max)()) {\n        typedef T *pointer;\n\n        gsl_SUPPRESS_MSVC_WARNING(26429,\n                                  \"f.23: symbol 'cur' is never tested for \"\n                                  \"nullness, it can be marked as not_null\") pointer cur = seq;\n\n        while (static_cast<SizeType>(cur - seq) < max && *cur != Sentinel)\n            ++cur;\n\n        gsl_Expects(*cur == Sentinel);\n\n        return span<T>(seq, gsl::narrow_cast<typename span<T>::index_type>(cur - seq));\n    }\n    }  // namespace detail\n\n    //\n    // ensure_z - creates a string_span for a czstring or cwzstring.\n    // Will fail fast if a null-terminator cannot be found before\n    // the limit of size_type.\n    //\n\n    template <class T>\n    gsl_NODISCARD inline gsl_constexpr14 span<T> ensure_z(\n            T *const &sz, size_t max = (std::numeric_limits<size_t>::max)()) {\n        return detail::ensure_sentinel<T, size_t, 0>(sz, max);\n    }\n\n    template <class T, size_t N>\n    gsl_NODISCARD inline gsl_constexpr14 span<T> ensure_z(T(&sz)[N]) {\n        return ::gsl::ensure_z(gsl_ADDRESSOF(sz[0]), N);\n    }\n\n#if gsl_HAVE(TYPE_TRAITS)\n\n    template <class Container>\n    gsl_NODISCARD inline gsl_constexpr14\n            span<typename std::remove_pointer<typename Container::pointer>::type>\n            ensure_z(Container & cont) {\n        return ::gsl::ensure_z(cont.data(), cont.length());\n    }\n#endif\n\n    //\n    // basic_zstring_span<> - A view of contiguous null-terminated characters,\n    // replace (*,len).\n    //\n\n    template <typename T>\n    class basic_zstring_span {\n    public:\n        typedef T element_type;\n        typedef span<T> span_type;\n\n        typedef typename span_type::index_type index_type;\n        typedef typename span_type::difference_type difference_type;\n\n        typedef element_type *czstring_type;\n        typedef basic_string_span<element_type> string_span_type;\n\n        gsl_api gsl_constexpr14 basic_zstring_span(span_type s) : span_(s) {\n            // expects a zero-terminated span\n            gsl_Expects(s.back() == '\\0');\n        }\n\n#if gsl_HAVE(IS_DEFAULT)\n        gsl_constexpr basic_zstring_span(basic_zstring_span const &) = default;\n        gsl_constexpr basic_zstring_span(basic_zstring_span &&) = default;\n        gsl_constexpr14 basic_zstring_span &operator=(basic_zstring_span const &) = default;\n        gsl_constexpr14 basic_zstring_span &operator=(basic_zstring_span &&) = default;\n#else\n        gsl_api gsl_constexpr basic_zstring_span(basic_zstring_span const &other)\n                : span_(other.span_) {}\n        gsl_api gsl_constexpr basic_zstring_span &operator=(basic_zstring_span const &other) {\n            span_ = other.span_;\n            return *this;\n        }\n#endif\n\n        gsl_NODISCARD gsl_api gsl_constexpr bool empty() const gsl_noexcept { return false; }\n\n        gsl_NODISCARD gsl_api gsl_constexpr string_span_type as_string_span() const gsl_noexcept {\n            return string_span_type(span_.data(), span_.size() - 1);\n        }\n\n        /*gsl_api*/  // currently disabled due to an apparent NVCC bug\n        gsl_NODISCARD gsl_constexpr string_span_type ensure_z() const {\n            return ::gsl::ensure_z(span_.data(), span_.size());\n        }\n\n        gsl_NODISCARD gsl_api gsl_constexpr czstring_type assume_z() const gsl_noexcept {\n            return span_.data();\n        }\n\n    private:\n        span_type span_;\n    };\n\n    //\n    // zString types:\n    //\n\n    typedef basic_zstring_span<char> zstring_span;\n    typedef basic_zstring_span<char const> czstring_span;\n\n#if gsl_HAVE(WCHAR)\n    typedef basic_zstring_span<wchar_t> wzstring_span;\n    typedef basic_zstring_span<wchar_t const> cwzstring_span;\n#endif\n\n}  // namespace gsl\n\n#if gsl_HAVE(HASH)\n\n//\n// std::hash specializations for GSL types\n//\n\nnamespace gsl {\n\nnamespace detail {\n\n//\n// Helper struct for std::hash specializations\n//\n\ntemplate <bool Condition>\nstruct conditionally_enabled_hash {};\n\n// disabled as described in [unord.hash]\ntemplate <>\nstruct conditionally_enabled_hash<false> {\n    gsl_is_delete_access : conditionally_enabled_hash() gsl_is_delete;\n    conditionally_enabled_hash(conditionally_enabled_hash const &) gsl_is_delete;\n    conditionally_enabled_hash(conditionally_enabled_hash &&) gsl_is_delete;\n    conditionally_enabled_hash &operator=(conditionally_enabled_hash const &) gsl_is_delete;\n    conditionally_enabled_hash &operator=(conditionally_enabled_hash &&) gsl_is_delete;\n};\n\n}  // namespace detail\n\n}  // namespace gsl\n\nnamespace std {\n\ntemplate <class T>\nstruct hash<::gsl::not_null<T>> : public ::gsl::detail::conditionally_enabled_hash<\n                                          is_default_constructible<hash<T>>::value> {\npublic:\n    gsl_NODISCARD gsl_constexpr std::size_t operator()(::gsl::not_null<T> const &v) const\n    // hash function is not `noexcept` because `as_nullable()` has preconditions\n    {\n        return hash<T>()(::gsl::as_nullable(v));\n    }\n};\ntemplate <class T>\nstruct hash<::gsl::not_null<T *>> {\npublic:\n    gsl_NODISCARD gsl_constexpr std::size_t operator()(::gsl::not_null<T *> const &v) const\n            gsl_noexcept {\n        return hash<T *>()(::gsl::as_nullable(v));\n    }\n};\n\ntemplate <>\nstruct hash<::gsl::byte> {\npublic:\n    gsl_NODISCARD gsl_constexpr std::size_t operator()(::gsl::byte v) const gsl_noexcept {\n        return ::gsl::to_integer<std::size_t>(v);\n    }\n};\n\n}  // namespace std\n\n#endif  // gsl_HAVE( HASH )\n\n#if gsl_FEATURE(GSL_LITE_NAMESPACE)\n\n// gsl_lite namespace:\n\n// gsl-lite currently keeps all symbols in the namespace `gsl`. The `gsl_lite`\n// namespace contains all the symbols in the `gsl` namespace, plus some\n// extensions that are not specified in the Core Guidelines.\n//\n// Going forward, we want to support coexistence of gsl-lite with M-GSL, so we\n// want to encourage using the `gsl_lite` namespace when consuming gsl-lite.\n// Typical use in library code would be:\n//\n//     #include <gsl-lite/gsl-lite.hpp>  // instead of <gsl/gsl-lite.hpp>\n//\n//     namespace foo {\n//         namespace gsl = ::gsl_lite;  // convenience alias\n//         double mean( gsl::span<double const> elements )\n//         {\n//             gsl_Expects( ! elements.empty() );  // instead of Expects()\n//             ...\n//         }\n//     } // namespace foo\n//\n// In a future version, the new <gsl-lite/gsl-lite.hpp> header will only define\n// the `gsl_lite` namespace and no unprefixed `Expects()` and `Ensures()` macros\n// to avoid collision with M-GSL. To ensure backward compatibility, the old\n// header <gsl/gsl-lite.hpp> will keep defining the `gsl` namespace and the\n// `Expects()` and `Ensures()` macros.\n\nnamespace gsl_lite {\n\nnamespace std11 = ::gsl::std11;\nnamespace std14 = ::gsl::std14;\nnamespace std17 = ::gsl::std17;\nnamespace std20 = ::gsl::std20;\n\nusing namespace std11;\nusing namespace std14;\nusing namespace std17;\nusing namespace std20;\n\nusing namespace ::gsl::detail::no_adl;\n\n#if gsl_HAVE(SHARED_PTR)\nusing std::make_shared;\nusing std::shared_ptr;\nusing std::unique_ptr;\n#endif\n\nusing ::gsl::diff;\nusing ::gsl::dim;\nusing ::gsl::index;\nusing ::gsl::stride;\n\n#if gsl_HAVE(ALIAS_TEMPLATE)\n#if gsl_BETWEEN(gsl_COMPILER_MSVC_VERSION, 1, \\\n                141)  // VS 2015 and earlier have trouble with `using` for alias \\\n                      // templates\ntemplate <class T\n#if gsl_HAVE(TYPE_TRAITS)\n          ,\n          typename = typename std::enable_if<std::is_pointer<T>::value>::type\n#endif\n          >\nusing owner = T;\n#else\nusing ::gsl::owner;\n#endif\n#endif\n\nusing ::gsl::fail_fast;\n\nusing ::gsl::finally;\n#if gsl_FEATURE(EXPERIMENTAL_RETURN_GUARD)\nusing ::gsl::on_error;\nusing ::gsl::on_return;\n#endif  // gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD )\n\nusing ::gsl::narrow;\nusing ::gsl::narrow_cast;\nusing ::gsl::narrow_failfast;\nusing ::gsl::narrowing_error;\n\nusing ::gsl::at;\n\nusing ::gsl::make_not_null;\nusing ::gsl::not_null;\nusing ::gsl::not_null_ic;\n\nusing ::gsl::byte;\n\nusing ::gsl::to_byte;\nusing ::gsl::to_integer;\nusing ::gsl::to_string;\nusing ::gsl::to_uchar;\n\nusing ::gsl::with_container;\nusing ::gsl::with_container_t;\n\nusing ::gsl::as_bytes;\nusing ::gsl::as_writable_bytes;\nusing ::gsl::byte_span;\nusing ::gsl::copy;\nusing ::gsl::make_span;\nusing ::gsl::span;\n#if !gsl_DEPRECATE_TO_LEVEL(6)\nusing ::gsl::as_writeable_bytes;\n#endif\n\nusing ::gsl::basic_string_span;\nusing ::gsl::cstring_span;\nusing ::gsl::string_span;\n\nusing ::gsl::basic_zstring_span;\nusing ::gsl::czstring_span;\nusing ::gsl::zstring_span;\n\nusing ::gsl::czstring;\nusing ::gsl::zstring;\n\n#if gsl_HAVE(WCHAR)\nusing ::gsl::cwzstring;\nusing ::gsl::wzstring;\n\nusing ::gsl::cwzstring_span;\nusing ::gsl::wzstring_span;\n#endif  // gsl_HAVE( WCHAR )\n\nusing ::gsl::ensure_z;\n\n}  // namespace gsl_lite\n\n#endif  // gsl_FEATURE( GSL_LITE_NAMESPACE )\n\ngsl_RESTORE_MSVC_WARNINGS()\n\n// #undef internal macros\n#undef gsl_STATIC_ASSERT_\n#undef gsl_ENABLE_IF_\n#undef gsl_TRAILING_RETURN_TYPE_\n#undef gsl_RETURN_DECLTYPE_\n\n#endif  // GSL_GSL_LITE_HPP_INCLUDED\n\n        // end of file\n"
  },
  {
    "path": "third_party/include/gsl.h",
    "content": "//\n// gsl-lite is based on GSL: Guidelines Support Library.\n// For more information see https://github.com/gsl-lite/gsl-lite\n//\n// Copyright (c) 2015 Martin Moene\n// Copyright (c) 2015 Microsoft Corporation. All rights reserved.\n//\n// This code is licensed under the MIT License (MIT).\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n// THE SOFTWARE.\n\n// mimic MS include hierarchy\n\n#ifndef GSL_GSL_H_INCLUDED\n#define GSL_GSL_H_INCLUDED\n\n#pragma message(\"gsl.h is deprecated since version 0.27.0, use gsl/gsl-lite.hpp instead.\")\n\n#include \"gsl/gsl-lite.hpp\"\n\n#endif  // GSL_GSL_H_INCLUDED\n"
  },
  {
    "path": "third_party/jsoncons-0.166-icc-fix.patch",
    "content": "commit 28c56b90ec7337f98a5b8942574590111a5e5831\nAuthor: Alex Merry <alex.merry@nanoporetech.com>\nDate:   Wed Aug 4 15:40:53 2021 +0100\n\n    Make implementation_type public in container types\n\n    This is used by jsoncons::type_traits::detail::basic_json_t in certain\n    circumstances, and ICC does not like that it is not publicly accessible.\n\n    Fixes compilation errors like:\n\n    jsoncons/basic_json.hpp(49):\n    error #525: type \"jsoncons::json_object<KeyT, Json,\n    std::enable_if<std::is_same<Json::implementation_policy::key_order,\n    jsoncons::sort_key_order>::value, void>::type>::implementation_policy\n    [with KeyT=std::basic_string<char, std::char_traits<char>,\n    std::allocator<char>>, Json=jsoncons::basic_json<char,\n    jsoncons::sorted_policy, std::allocator<char>>]\" (declared at line 541\n    of \"jsoncons/json_container_types.hpp\")\n    is an inaccessible type (allowed for cfront compatibility)\n    basic_json_t = basic_json<typename T::char_type,typename\n    T::implementation_policy,typename T::allocator_type>;\n    detected during:\n        instantiation of type\n        \"jsoncons::type_traits::detail::basic_json_t<\n        jsoncons::json_object<std::basic_string<char,\n        std::char_traits<char>, std::allocator<char>>,\n        jsoncons::basic_json<char, jsoncons::sorted_policy,\n        std::allocator<char>>, void>>\" at line 182 of\n        \"jsoncons/more_type_traits.hpp\"\n\ndiff --git a/include/jsoncons/json_container_types.hpp b/include/jsoncons/json_container_types.hpp\nindex afe10d4..ced66e6 100644\n--- a/include/jsoncons/json_container_types.hpp\n+++ b/include/jsoncons/json_container_types.hpp\n@@ -32,8 +32,8 @@ namespace jsoncons {\n     public:\n         using allocator_type = typename Json::allocator_type;\n         using value_type = Json;\n-    private:\n         using implementation_policy = typename Json::implementation_policy;\n+    private:\n         using value_allocator_type = typename std::allocator_traits<allocator_type>:: template rebind_alloc<value_type>;\n         using value_container_type = typename implementation_policy::template sequence_container_type<value_type,value_allocator_type>;\n         value_container_type elements_;\n@@ -537,8 +537,8 @@ namespace jsoncons {\n         using key_value_type = key_value<KeyT,Json>;\n         using char_type = typename Json::char_type;\n         using string_view_type = typename Json::string_view_type;\n-    private:\n         using implementation_policy = typename Json::implementation_policy;\n+    private:\n         using key_value_allocator_type = typename std::allocator_traits<allocator_type>:: template rebind_alloc<key_value_type>;\n         using key_value_container_type = typename implementation_policy::template sequence_container_type<key_value_type,key_value_allocator_type>;\n\n@@ -1235,8 +1235,8 @@ namespace jsoncons {\n         //using mapped_type = Json;\n         using string_view_type = typename Json::string_view_type;\n         using key_value_type = key_value<KeyT,Json>;\n-    private:\n         using implementation_policy = typename Json::implementation_policy;\n+    private:\n         using key_value_allocator_type = typename std::allocator_traits<allocator_type>:: template rebind_alloc<key_value_type>;\n         using key_value_container_type = typename implementation_policy::template sequence_container_type<key_value_type,key_value_allocator_type>;\n         typedef typename std::allocator_traits<allocator_type>:: template rebind_alloc<std::size_t> index_allocator_type;\n"
  },
  {
    "path": "third_party/licenses/catch2.txt",
    "content": "Boost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "third_party/licenses/gsl-lite.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Martin Moene\nCopyright (c) 2015 Microsoft Corporation. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "third_party/software_versions.yaml",
    "content": "# Structure:\n# <name (use correct capitalisation, spaces, etc - quote if necessary)>:\n#     version: \"<what version is included>\"\n#     license: \"<path to a file in the licenses dir>\"\n#     files:\n#         - shell-style globs of files in include\n#\n# This information is both useful to developers and used to build third-party software information\n# documents to include in our distributions (many open source licenses require this, and it's\n# generally a good thing to do regardless).\n#\n# The license file should ideally be copied directly from the source distribution.\n#\n# The \"files\" field is used by the CI jobs to check that this file has been assembled correctly, and\n# we didn't forget any licenses.\n---\nCatch2:\n    description: >\n        Unit testing framework. Nicer to use than most other C++ unit testing frameworks.\n    # Omitted from third-party license files because it's not in any shipped code\n    omit: True\n    version: \"2.13.7\"\n    url: https://github.com/catchorg/Catch2\n    license: licenses/catch2.txt\n    files:\n        - include/catch2/catch.hpp\nGSL Lite:\n    description: >\n        Functions and types suggested for use by the C++ Core Guidelines\n        <https://github.com/isocpp/CppCoreGuidelines>. In the future we may want to switch to\n        Microsoft's implementation <https://github.com/microsoft/gsl>, but that doesn't support\n        GCC 4.8.\n    version: \"0.38.1\"\n    url: https://github.com/martinmoene/gsl-lite\n    license: licenses/gsl-lite.txt\n    files:\n        - include/gsl.h\n        - include/gsl\npybind11:\n    description: >\n        pybind11 is a lightweight header-only library that exposes C++ types in Python\n        and vice versa, mainly to create Python bindings of existing C++ code.\n    version: \"2.10.1\"\n    url: https://github.com/pybind/pybind11\n    license: licenses/pybind11.txt\n    files:\n        - include/pybind11/*\n"
  }
]